Uh oh!
There was an error while loading. Please reload this page.
[Opt](cloud) Add rate limit for BE to MS rpc - #60344
Conversation
hello-stephen
commented
Jan 29, 2026
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
28843ee to
6e33c81Compare82ed77c to
a8239acComparebobhan1
commented
Feb 11, 2026
run buildall |
doris-robot
commented
Feb 11, 2026
Cloud UT Coverage ReportIncrement line coverage Increment coverage report
|
hello-stephen
commented
Feb 11, 2026
FE UT Coverage ReportIncrement line coverage |
doris-robot
commented
Feb 11, 2026
TPC-H: Total hot run time: 30385 ms |
doris-robot
commented
Feb 11, 2026
ClickBench: Total hot run time: 28.3 s |
hello-stephen
commented
Feb 11, 2026
BE UT Coverage ReportIncrement line coverage Increment coverage report
|
bdd55d6 to
b345ed1Comparebobhan1
commented
Feb 27, 2026
run buildall |
93f86d5 to
bb1452bComparedoris-robot
commented
Feb 27, 2026
Cloud UT Coverage ReportIncrement line coverage Increment coverage report
|
PR approved by at least one committer and no changes requested. |
PR approved by anyone and no changes requested. |
bobhan1
commented
May 6, 2026
run buildall |
fix [improvement](be) Hook dynamic MS throttle configs to update callbacks Issue Number: None Related PR: None Problem Summary: Newly added BE configs for per-RPC MS QPS limits and MS backpressure throttle upgrade/downgrade only changed config values at runtime, but did not propagate those changes into the in-memory rate limiter and backpressure handler state. This commit registers DEFINE_ON_UPDATE callbacks for those configs and refreshes the corresponding runtime objects only when the new value differs from the old value. None - Test: No need to test (code change committed without rerunning build in this step) - Behavior changed: Yes (runtime config updates now take effect on the corresponding in-memory MS throttling state) - Does this need documentation: No update fix sync rowset retry and fix MSBackpressureHandler state transition is not atomic fix wrong substitution [fix](be) Log actual throttle ticks on transition Issue Number: None Related PR: None Problem Summary: Capture the actual elapsed tick counters before resetting them so the ms-throttle upgrade and downgrade logs report real values instead of reset counters. None - Test: No need to test (log-only change; attempted targeted BE UT but sandbox blocked submodule update) - Behavior changed: Yes (INFO logs now print the actual elapsed ticks for upgrade and downgrade triggers) - Does this need documentation: No [fix](be) Disable MS backpressure handling by default Issue Number: None Related PR: None Problem Summary: Change the default value of enable_ms_backpressure_handling to false so MS backpressure response handling is opt-in instead of enabled by default. MS backpressure handling is now disabled by default. - Test: No need to test (single default-config change only) - Behavior changed: Yes (enable_ms_backpressure_handling defaults to false) - Does this need documentation: No format change enable_ms_rpc_host_level_rate_limit default to falase
### What problem does this PR solve? Issue Number: None Related PR: None Problem Summary: ExecEnv forward-declared doris::cloud MS RPC limiter types, which exposed doris::cloud through common include paths and made older headers resolve global cloud protobuf types incorrectly. ### Release note None ### Check List (For Author) - Test: Manual test - ./build.sh --be -j100 - Behavior changed: No - Does this need documentation: No
bobhan1
commented
May 7, 2026
run buildall |
PR approved by at least one committer and no changes requested. |
Uh oh!
There was an error while loading. Please reload this page.
This PR implements a two-layer MS (Meta Service) RPC rate limiting system for Doris cloud mode BE: 1. **Host-level rate limiting** — Token-bucket based QPS limiter for all 21 MS RPC types, preventing a single BE from overwhelming the MS with burst traffic. 2. **Table-level adaptive backpressure** — When MS returns `MS_BUSY` error code, BE dynamically identifies and throttles the top-k highest-QPS tables using a state machine, and automatically relaxes limits after the pressure subsides. --- ## Part 1: BE Host-Level Rate Limiting ### Problem In cloud mode, all BE nodes send RPCs (get_tablet, prepare_rowset, commit_rowset, etc.) to a shared Meta Service. A single BE experiencing load spikes (e.g., large batch imports, compaction storms) can send excessive RPC traffic that overwhelms MS, degrading service for all BEs. ### Solution Introduce `HostLevelMSRpcRateLimiters` — a per-BE, per-RPC-type rate limiter using token bucket algorithm. **Architecture:** - All 21 MS RPC types are enumerated in `MetaServiceRPC` enum (defined via X-macro for maintainability) - Each RPC type has an independent `TokenBucketRateLimiterHolder` with its own QPS limit - QPS limits are configured per CPU core: `actual_qps = config_value × num_cores` - Thread-safe design using `atomic_shared_ptr<RpcRateLimiter>` array for lock-free concurrent access during `limit()` calls - Each rate limiter includes a `bvar::LatencyRecorder` to monitor sleep durations caused by rate limiting **Configuration:** | Config | Default | Description | |--------|---------|-------------| | `enable_ms_rpc_host_level_rate_limit` | `true` | Global enable/disable switch | | `ms_rpc_qps_default` | `100` | Default per-core QPS for all RPCs | | `ms_rpc_qps_<rpc_name>` | `-1` | Per-RPC override (`-1` = use default, `0` = disabled) | All QPS configs are mutable (`DEFINE_mInt32`), allowing runtime adjustment without restart. `reset_all()` re-reads configs and recreates rate limiters. **Integration:** Rate limiting is applied inside the `retry_rpc()` template function in `cloud_meta_mgr.cpp`, which wraps all MS RPC calls. The `RpcRateLimitCtx` struct carries the rate limiter reference. Rate limiting executes before each RPC attempt (including retries), with the call to `apply_rate_limit()` performing a `bthread_usleep` if the token bucket requires waiting. **New files:** - `be/src/cloud/cloud_ms_rpc_rate_limiters.h` / `.cpp` - `be/test/cloud/cloud_ms_rpc_rate_limiters_test.cpp` --- ## Part 2: BE Table-Level Adaptive Backpressure ### Problem Host-level rate limiting applies uniformly across all tables. When MS reports overload (`MAX_QPS_LIMIT`), it's often caused by a small number of high-traffic tables (e.g., tables with many concurrent stream load jobs). A uniform rate limit would unnecessarily penalize all tables, while the hot tables continue to dominate the RPC traffic. ### Solution Implement table-level adaptive throttling for load-related RPCs. When MS returns `MAX_QPS_LIMIT`, BE identifies the top-k highest-QPS tables and progressively reduces their QPS limits, while leaving other tables unaffected. **Scope:** Only 5 load-related RPC types participate in table-level throttling: - `PREPARE_ROWSET` - `COMMIT_ROWSET` - `UPDATE_TMP_ROWSET` - `UPDATE_PACKED_FILE_INFO` - `UPDATE_DELETE_BITMAP` **Architecture (4 components with clear separation of concerns):** ``` MS_BUSY signal (MAX_QPS_LIMIT) │ ▼ ┌─────────────────────────┐ ┌──────────────────────────┐ │ RpcThrottleCoordinator │──────▶│ RpcThrottleStateMachine │ │ (timing control) │ │ (pure state logic) │ │ - upgrade cooldown │ │ - upgrade history stack │ │ - downgrade trigger │ │ - limit calculation │ └─────────────────────────┘ └──────────┬───────────────┘ │ Actions ▼ ┌─────────────────────────┐ ┌──────────────────────────┐ │ TableRpcQpsRegistry │ │ TableRpcThrottler │ │ (QPS statistics) │ │ (limit enforcement) │ │ - per-table bvar │ │ - StrictQpsLimiter │ │ - top-k query │ │ - per (rpc, table) │ └─────────────────────────┘ └──────────────────────────┘ ``` **Component details:** 1. **`TableRpcQpsRegistry`** — Tracks per-(rpc_type, table_id) QPS using `bvar::PerSecond<bvar::Adder>`. Supports efficient top-k query via min-heap. Configurable time window via `ms_rpc_table_qps_window_sec` (immutable, default 10s). 2. **`RpcThrottleStateMachine`** — Pure state machine with no time awareness or side effects. Maintains upgrade history as a stack for clean rollback. - `on_upgrade(snapshot)`: For each top-k table in the QPS snapshot, calculates `new_limit = current_qps × ratio` (first time) or `current_limit × ratio` (already limited), with a floor of `ms_rpc_table_qps_limit_floor`. Returns `SET_LIMIT` actions. - `on_downgrade()`: Pops the most recent upgrade from history. If the table had a prior limit, restores it (`SET_LIMIT`). If no prior limit, removes it (`REMOVE_LIMIT`). 3. **`RpcThrottleCoordinator`** — Timing control layer using tick counts (1 tick = 1 ms). - `report_ms_busy()`: Returns true if enough ticks have passed since last upgrade (cooldown). - `tick(n)`: Advances time by n ticks. Returns true if downgrade should trigger (no MS_BUSY for `downgrade_after_ticks`). 4. **`TableRpcThrottler`** — Enforces QPS limits using `StrictQpsLimiter` (strict fixed-interval, no burst allowed). Each (rpc_type, table_id) pair has its own limiter. Returns the time point when the request may execute; the caller sleeps until then. 5. **`MSBackpressureHandler`** — Orchestrator that wires all components together: - `on_ms_busy()`: Called when `retry_rpc` receives `MAX_QPS_LIMIT`. Consults coordinator for cooldown, builds QPS snapshot from registry, feeds to state machine, applies resulting actions to throttler. - `before_rpc()` / `after_rpc()`: Called around each load-related RPC for throttle enforcement and QPS recording. - Background tick thread: Runs every 1 second, advances coordinator by 1000 ticks. Triggers downgrade when enough time has passed without MS_BUSY. **Upgrade/Downgrade lifecycle example:** ``` Time 0s: MS returns MAX_QPS_LIMIT → Upgrade level 1: top-2 tables (A: 100 qps, B: 80 qps) → A limited to 50 qps, B limited to 40 qps Time 2s: MS returns MAX_QPS_LIMIT again (cooldown 5s not passed) → Skipped Time 6s: MS returns MAX_QPS_LIMIT (cooldown passed) → Upgrade level 2: top-2 tables now (A: 50 qps, C: 60 qps) → A limited to 25 qps, C limited to 30 qps Time 11s: No MS_BUSY for 5s → Downgrade: undo level 2 → A restored to 50 qps, C limit removed Time 16s: No MS_BUSY for 5s → Downgrade: undo level 1 → A limit removed, B limit removed ``` **Configuration:** | Config | Default | Mutable | Description | |--------|---------|---------|-------------| | `enable_ms_backpressure_handling` | `false` | Yes | Global enable/disable switch | | `ms_rpc_table_qps_window_sec` | `3` | No | bvar time window for QPS calculation | | `ms_backpressure_upgrade_interval_ms` | `3000` | Yes | Minimum cooldown between upgrades | | `ms_backpressure_upgrade_top_k` | `2` | Yes | Number of top tables to throttle per upgrade | | `ms_backpressure_throttle_ratio` | `0.75` | Yes | QPS decay ratio on upgrade | | `ms_rpc_table_qps_limit_floor` | `1.0` | Yes | Minimum QPS limit (won't throttle below this) | | `ms_backpressure_downgrade_interval_ms` | `3000` | Yes | Time without MS_BUSY before downgrade | **Observability (bvar metrics):** - `ms_rpc_backpressure_upgrade_count` / `_60s` — Upgrade event counts - `ms_rpc_backpressure_downgrade_count` / `_60s` — Downgrade event counts - `ms_rpc_backpressure_ms_busy_count` / `_60s` — MS_BUSY signal counts - `ms_rpc_backpressure_throttle_wait_<rpc_name>` — Per-RPC-type throttle wait latency - `ms_rpc_backpressure_throttled_tables_<rpc_name>` — Number of throttled tables per RPC type **New files:** - `be/src/cloud/cloud_throttle_state_machine.h` / `.cpp` - `be/src/cloud/cloud_ms_backpressure_handler.h` / `.cpp` - `be/test/cloud/cloud_throttle_state_machine_test.cpp` - `be/test/cloud/cloud_ms_backpressure_handler_test.cpp` **Also renamed (not part of the feature, cleanup):** - `common/cpp/s3_rate_limiter.h/.cpp` → `common/cpp/token_bucket_rate_limiter.h/.cpp` (more general naming since it's now used beyond S3) ## Part 3: System Table for Table-Level Throttler Observability ### Problem The table-level backpressure system operates transparently inside BE. When issues arise, users and DBAs have no way to inspect which tables are being throttled, what their QPS limits are, or what their current QPS is — beyond checking raw bvar metrics. ### Solution Add a new system table `information_schema.backend_ms_rpc_table_throttlers` that exposes the real-time state of the `TableRpcThrottler` on each BE. This table is a **Backend-Partitioned Schema Table**, meaning each BE reports its own throttling data, and queries are distributed to all alive BEs and aggregated. **Schema:** | Column | Type | Description | |--------|------|-------------| | `BE_ID` | BIGINT | Backend ID | | `TABLE_ID` | BIGINT | Table ID being throttled | | `RPC_TYPE` | VARCHAR(64) | RPC type name (e.g., `PREPARE_ROWSET`, `COMMIT_ROWSET`) | | `QPS_LIMIT` | DOUBLE | Current QPS limit enforced on this (table, rpc) pair | | `CURRENT_QPS` | DOUBLE | Current observed QPS for this (table, rpc) pair | **Usage examples:** ```sql -- View all currently throttled tables across all BEs SELECT * FROM information_schema.backend_ms_rpc_table_throttlers; -- View throttled tables on a specific BE SELECT * FROM information_schema.backend_ms_rpc_table_throttlers WHERE BE_ID = 10001; -- Find the most severely throttled tables SELECT * FROM information_schema.backend_ms_rpc_table_throttlers ORDER BY QPS_LIMIT ASC; ``` ### Release note None ### Check List (For Author) - Test <!-- At least one of them must be included. --> - [x] Regression test - [x] Unit Test - [ ] Manual test (add detailed scripts or steps below) - [ ] No need to test or manual test. Explain why: - [ ] This is a refactor/code format and no logic has been changed. - [ ] Previous test can cover this change. - [ ] No code files have been changed. - [ ] Other reason <!-- Add your reason? --> - Behavior changed: - [ ] No. - [x] Yes. <!-- Explain the behavior change --> - Added host-level token-bucket rate limiting for all MS RPCs (enabled by default via `enable_ms_rpc_host_level_rate_limit`) - Added table-level adaptive backpressure handling triggered by MS `MAX_QPS_LIMIT` response (disabled by default via `enable_ms_backpressure_handling`)
## Proposed changes Backport #60344 / eaa4ef9 to branch-4.1. This adds BE-to-MS RPC throttling support, including: - host-level MS RPC rate limiters - table-level MS backpressure throttling - schema table exposure for backend MS RPC table throttlers - token bucket rate limiter naming updates used by common S3/rate-limit code Conflict resolution notes: - Kept branch-4.1 schema table numbering and added SCH_BACKEND_MS_RPC_TABLE_THROTTLERS after the existing branch-local entries. - Kept branch-4.1 cloud config layout and added the new MS RPC throttling configs before the compile-check boundary. ## Validation - `git diff HEAD^ HEAD --check` - `./run-be-ut.sh --run --filter=HostLevelMSRpcRateLimitersTest.*:HostLevelMSRpcRateLimitersConfigTest.*:StrictQpsLimiterTest.*:TableRpcQpsRegistryTest.*:TableRpcThrottlerTest.*:MSBackpressureHandlerTest.*:LoadRelatedRpcTest.*:RpcThrottleStateMachineTest.*:RpcThrottleCoordinatorTest.*:RpcThrottleIntegrationTest.* -j32` - 71 tests from 10 test suites passed.
What problem does this PR solve?
Problem Summary:
This PR implements a two-layer MS (Meta Service) RPC rate limiting system for Doris cloud mode BE:
MS_BUSYerror code, BE dynamically identifies and throttles the top-k highest-QPS tables using a state machine, and automatically relaxes limits after the pressure subsides.Part 1: BE Host-Level Rate Limiting
Problem
In cloud mode, all BE nodes send RPCs (get_tablet, prepare_rowset, commit_rowset, etc.) to a shared Meta Service. A single BE experiencing load spikes (e.g., large batch imports, compaction storms) can send excessive RPC traffic that overwhelms MS, degrading service for all BEs.
Solution
Introduce
HostLevelMSRpcRateLimiters— a per-BE, per-RPC-type rate limiter using token bucket algorithm.Architecture:
MetaServiceRPCenum (defined via X-macro for maintainability)TokenBucketRateLimiterHolderwith its own QPS limitactual_qps = config_value × num_coresatomic_shared_ptr<RpcRateLimiter>array for lock-free concurrent access duringlimit()callsbvar::LatencyRecorderto monitor sleep durations caused by rate limitingConfiguration:
enable_ms_rpc_host_level_rate_limittruems_rpc_qps_default100ms_rpc_qps_<rpc_name>-1-1= use default,0= disabled)All QPS configs are mutable (
DEFINE_mInt32), allowing runtime adjustment without restart.reset_all()re-reads configs and recreates rate limiters.Integration:
Rate limiting is applied inside the
retry_rpc()template function incloud_meta_mgr.cpp, which wraps all MS RPC calls. TheRpcRateLimitCtxstruct carries the rate limiter reference. Rate limiting executes before each RPC attempt (including retries), with the call toapply_rate_limit()performing abthread_usleepif the token bucket requires waiting.New files:
be/src/cloud/cloud_ms_rpc_rate_limiters.h/.cppbe/test/cloud/cloud_ms_rpc_rate_limiters_test.cppPart 2: BE Table-Level Adaptive Backpressure
Problem
Host-level rate limiting applies uniformly across all tables. When MS reports overload (
MAX_QPS_LIMIT), it's often caused by a small number of high-traffic tables (e.g., tables with many concurrent stream load jobs). A uniform rate limit would unnecessarily penalize all tables, while the hot tables continue to dominate the RPC traffic.Solution
Implement table-level adaptive throttling for load-related RPCs. When MS returns
MAX_QPS_LIMIT, BE identifies the top-k highest-QPS tables and progressively reduces their QPS limits, while leaving other tables unaffected.Scope: Only 5 load-related RPC types participate in table-level throttling:
PREPARE_ROWSETCOMMIT_ROWSETUPDATE_TMP_ROWSETUPDATE_PACKED_FILE_INFOUPDATE_DELETE_BITMAPArchitecture (4 components with clear separation of concerns):
Component details:
TableRpcQpsRegistry— Tracks per-(rpc_type, table_id) QPS usingbvar::PerSecond<bvar::Adder>. Supports efficient top-k query via min-heap. Configurable time window viams_rpc_table_qps_window_sec(immutable, default 10s).RpcThrottleStateMachine— Pure state machine with no time awareness or side effects. Maintains upgrade history as a stack for clean rollback.on_upgrade(snapshot): For each top-k table in the QPS snapshot, calculatesnew_limit = current_qps × ratio(first time) orcurrent_limit × ratio(already limited), with a floor ofms_rpc_table_qps_limit_floor. ReturnsSET_LIMITactions.on_downgrade(): Pops the most recent upgrade from history. If the table had a prior limit, restores it (SET_LIMIT). If no prior limit, removes it (REMOVE_LIMIT).RpcThrottleCoordinator— Timing control layer using tick counts (1 tick = 1 ms).report_ms_busy(): Returns true if enough ticks have passed since last upgrade (cooldown).tick(n): Advances time by n ticks. Returns true if downgrade should trigger (no MS_BUSY fordowngrade_after_ticks).TableRpcThrottler— Enforces QPS limits usingStrictQpsLimiter(strict fixed-interval, no burst allowed). Each (rpc_type, table_id) pair has its own limiter. Returns the time point when the request may execute; the caller sleeps until then.MSBackpressureHandler— Orchestrator that wires all components together:on_ms_busy(): Called whenretry_rpcreceivesMAX_QPS_LIMIT. Consults coordinator for cooldown, builds QPS snapshot from registry, feeds to state machine, applies resulting actions to throttler.before_rpc()/after_rpc(): Called around each load-related RPC for throttle enforcement and QPS recording.Upgrade/Downgrade lifecycle example:
Configuration:
enable_ms_backpressure_handlingfalsems_rpc_table_qps_window_sec3ms_backpressure_upgrade_interval_ms3000ms_backpressure_upgrade_top_k2ms_backpressure_throttle_ratio0.75ms_rpc_table_qps_limit_floor1.0ms_backpressure_downgrade_interval_ms3000Observability (bvar metrics):
ms_rpc_backpressure_upgrade_count/_60s— Upgrade event countsms_rpc_backpressure_downgrade_count/_60s— Downgrade event countsms_rpc_backpressure_ms_busy_count/_60s— MS_BUSY signal countsms_rpc_backpressure_throttle_wait_<rpc_name>— Per-RPC-type throttle wait latencyms_rpc_backpressure_throttled_tables_<rpc_name>— Number of throttled tables per RPC typeNew files:
be/src/cloud/cloud_throttle_state_machine.h/.cppbe/src/cloud/cloud_ms_backpressure_handler.h/.cppbe/test/cloud/cloud_throttle_state_machine_test.cppbe/test/cloud/cloud_ms_backpressure_handler_test.cppAlso renamed (not part of the feature, cleanup):
common/cpp/s3_rate_limiter.h/.cpp→common/cpp/token_bucket_rate_limiter.h/.cpp(more general naming since it's now used beyond S3)Part 3: System Table for Table-Level Throttler Observability
Problem
The table-level backpressure system operates transparently inside BE. When issues arise, users and DBAs have no way to inspect which tables are being throttled, what their QPS limits are, or what their current QPS is — beyond checking raw bvar metrics.
Solution
Add a new system table
information_schema.backend_ms_rpc_table_throttlersthat exposes the real-time state of theTableRpcThrottleron each BE. This table is a Backend-Partitioned Schema Table, meaning each BE reports its own throttling data, and queries aredistributed to all alive BEs and aggregated.
Schema:
BE_IDTABLE_IDRPC_TYPEPREPARE_ROWSET,COMMIT_ROWSET)QPS_LIMITCURRENT_QPSUsage examples:
Release note
None
Check List (For Author)
Test
Behavior changed:
enable_ms_rpc_host_level_rate_limit)MAX_QPS_LIMITresponse (disabled by default viaenable_ms_backpressure_handling)Does this need documentation?
Check List (For Reviewer who merge this PR)