github-actions[bot] commented on code in PR #66977:
URL: https://github.com/apache/doris/pull/66977#discussion_r3859642655
##########
be/src/cloud/cloud_ms_backpressure_handler.cpp:
##########
@@ -124,42 +142,59 @@ double TableRpcQpsCounter::get_qps() const {
// ============== TableRpcQpsRegistry ==============
-TableRpcQpsRegistry::TableRpcQpsRegistry() = default;
+TableRpcQpsRegistry::TableRpcQpsRegistry()
+ : TableRpcQpsRegistry(kQpsRegistryCleanupInterval,
qps_registry_inactive_timeout()) {}
-void TableRpcQpsRegistry::record(LoadRelatedRpc rpc_type, int64_t table_id) {
- auto* counter = get_or_create_counter(rpc_type, table_id);
- if (counter) {
- counter->increment();
+TableRpcQpsRegistry::TableRpcQpsRegistry(std::chrono::milliseconds
cleanup_interval,
+ std::chrono::milliseconds
inactive_timeout)
+ : _cleanup_interval(cleanup_interval),
+ _inactive_timeout(inactive_timeout),
+ _cleanup_stop_latch(1) {
+ DORIS_CHECK_GT(_cleanup_interval.count(), 0);
+ DORIS_CHECK_GE(_inactive_timeout.count(), 0);
+
+ auto st = Thread::create(
+ "TableRpcQpsRegistry", "cleanup_thread", [this]() {
this->_cleanup_thread_callback(); },
+ &_cleanup_thread);
+ if (!st.ok()) {
+ LOG(WARNING) << "[ms-throttle] failed to create table QPS registry
cleanup thread: " << st;
}
}
-TableRpcQpsCounter* TableRpcQpsRegistry::get_or_create_counter(LoadRelatedRpc
rpc_type,
- int64_t
table_id) {
+TableRpcQpsRegistry::~TableRpcQpsRegistry() {
+ _cleanup_stop_latch.count_down();
+ if (_cleanup_thread) {
+ _cleanup_thread->join();
+ }
+}
+
+void TableRpcQpsRegistry::record(LoadRelatedRpc rpc_type, int64_t table_id) {
size_t idx = static_cast<size_t>(rpc_type);
if (idx >= static_cast<size_t>(LoadRelatedRpc::COUNT)) {
- return nullptr;
+ return;
}
{
std::shared_lock lock(_mutex);
auto it = _counters[idx].find(table_id);
if (it != _counters[idx].end()) {
- return it->second.get();
+ it->second->increment();
+ return;
}
}
std::unique_lock lock(_mutex);
// Double check after acquiring exclusive lock
auto it = _counters[idx].find(table_id);
if (it != _counters[idx].end()) {
- return it->second.get();
+ it->second->increment();
+ return;
}
auto counter = std::make_unique<TableRpcQpsCounter>(table_id, rpc_type,
config::ms_rpc_table_qps_window_sec);
- auto* ptr = counter.get();
+ counter->increment();
Review Comment:
[P2] Construct first-touch bvars outside the registry lock
After the shared-lock miss, this path holds the single registry-wide write
lock while allocating and constructing both the `bvar::Adder` and
`bvar::PerSecond`. With collection now enabled by default, rotating table IDs
serialize unrelated RPC types and block existing-table `record()`, `get_qps()`,
and MS_BUSY top-k scans behind every first-touch bvar construction. This is
distinct from the earlier eviction comment: current head moved bvar destruction
outside the lock, but create-side work remains inside it. Please build a
prospective counter before locking, double-check/insert under the lock, destroy
any race loser after unlocking, and add a concurrent first-touch
progress/latency test.
##########
be/src/cloud/cloud_ms_backpressure_handler.cpp:
##########
@@ -245,15 +329,35 @@ TableRpcThrottler::TableRpcThrottler() {
std::chrono::steady_clock::time_point
TableRpcThrottler::throttle(LoadRelatedRpc rpc_type,
int64_t
table_id) {
+ return throttle(rpc_type, table_id, false).wait_until;
+}
+
+TableRpcThrottleDecision TableRpcThrottler::throttle(LoadRelatedRpc rpc_type,
int64_t table_id,
+ bool dry_run) {
std::shared_lock lock(_mutex);
auto it = _limiters.find({rpc_type, table_id});
if (it == _limiters.end()) {
- return std::chrono::steady_clock::now();
+ return {.wait_until = std::chrono::steady_clock::now(), .dry_run =
dry_run};
}
- return it->second->reserve();
+ return {
+ .wait_until = it->second->reserve(),
Review Comment:
[P1] Bound the dry-run reservation horizon
Dry-run still calls `reserve()` here for every request but skips the
corresponding sleep. Once persistent MS_BUSY has driven a hot table to the
default 1-QPS floor, 100,000 dry-run calls/s advance `_next_allowed_time` by
100,000 seconds per wall-clock second, exhausting an int64 nanosecond
`steady_clock::time_point` in about 26 hours (about 11 days at 10,000 calls/s).
The next duration addition overflows, and an ordinary wrap makes the deadline
look elapsed, corrupting would-wait observations and any later enforcement. The
existing transition-debt thread does not cover permanent dry-run, because
resetting only when enforcement is enabled still leaves this path unbounded.
Please cap/rebase dry-run reservations or use a separately bounded observation
model, with a saturation-boundary test.
##########
be/src/cloud/cloud_meta_mgr.cpp:
##########
@@ -493,33 +494,54 @@ struct RpcRateLimitCtx {
int64_t table_id {-1}; // For table-level backpressure, passed from caller
};
-// Apply rate limiting before RPC (both host-level and table-level)
-void apply_rate_limit(MetaServiceRPC rpc, const RpcRateLimitCtx& ctx) {
- // Table-level rate limit (for load-related RPCs only)
- if (ctx.backpressure_handler && ctx.table_id > 0) {
- LoadRelatedRpc load_rpc = to_load_related_rpc(rpc);
- if (load_rpc != LoadRelatedRpc::COUNT) {
- auto wait_until = ctx.backpressure_handler->before_rpc(load_rpc,
ctx.table_id);
- auto now = std::chrono::steady_clock::now();
- if (wait_until > now) {
- auto wait_us =
-
std::chrono::duration_cast<std::chrono::microseconds>(wait_until - now)
- .count();
- if (wait_us > 0) {
- if (auto* recorder = get_throttle_wait_recorder(load_rpc);
- recorder != nullptr) {
- *recorder << wait_us;
- }
- bthread_usleep(wait_us);
- }
- }
- }
+void apply_table_level_rate_limit(MetaServiceRPC rpc, const RpcRateLimitCtx&
ctx) {
+ if (ctx.backpressure_handler == nullptr || ctx.table_id <= 0) {
+ return;
}
- // Host-level rate limit
- if (ctx.host_limiters) {
- ctx.host_limiters->limit(rpc);
+ const auto load_rpc = to_load_related_rpc(rpc);
+ if (load_rpc == LoadRelatedRpc::COUNT) {
+ return;
+ }
+
+ const auto decision = ctx.backpressure_handler->before_rpc(load_rpc,
ctx.table_id);
+ const auto now = std::chrono::steady_clock::now();
+ if (decision.wait_until <= now) {
+ return;
+ }
+
+ const auto wait_us =
+
std::chrono::duration_cast<std::chrono::microseconds>(decision.wait_until - now)
+ .count();
+ if (wait_us <= 0) {
+ return;
+ }
+
+ auto* recorder = get_throttle_wait_recorder(load_rpc);
+ DCHECK(recorder);
+ *recorder << wait_us;
Review Comment:
[P1] Keep dry-run waits within the recorder range
This insertion happens before the once-per-second log gate, but the pinned
brpc 1.4 `LatencyRecorder` forwards to an `IntRecorder` that [clamps samples
above `INT_MAX` and emits a WARNING on every
insertion](https://github.com/apache/brpc/blob/1.4.0/src/bvar/recorder.h#L218-L266);
its percentile path also narrows to `uint32_t`. `INT_MAX` microseconds is only
about 2,147 seconds of modeled wait. At the 1-QPS table floor, 1,000 unslept
dry-run requests/s cross that bound in about 2.15 seconds, after which every
request can warn and the metric is no longer representative. The host dry-run
recorder has the same record-before-log path. This occurs long before the
underlying limiter states overflow and needs a metric-aware bound. Please
saturate/encode the sample explicitly (with an overflow counter or wider
metric) before insertion and add host/table boundary tests that prove no
per-request warnings.
##########
be/src/cloud/cloud_ms_rpc_rate_limiters.cpp:
##########
@@ -139,10 +148,21 @@ int64_t HostLevelMSRpcRateLimiters::limit(MetaServiceRPC
rpc) {
}
auto limiter = _limiters[idx].load();
- if (limiter && limiter->limiter) {
- return limiter->limiter->add(1);
+ if (!limiter) {
+ return 0;
+ }
+ DCHECK(limiter->limiter);
+
+ auto result = dry_run ? limiter->limiter->reserve_with_config(1)
Review Comment:
[P1] Bound the host dry-run wait calculation
This new dry-run branch reserves every request without sleeping, so traffic
above the host QPS limit drives the shared bucket debt downward without bound.
`reserve()` later computes that debt as a `double` nanosecond wait and casts it
directly to `int64_t`; once the modeled wait exceeds about 292 years, the
conversion is out of range and has undefined behavior. At a supported 1-QPS
limit, 100,000 calls/s reaches that boundary in about 26 hours (and a 100-QPS
limit on a one-core BE in about 107 days), after which observations and any
later enforcement can become non-positive or otherwise invalid. Resetting only
when enforcement is enabled would address the existing transition-debt thread
but not permanent dry-run. Please cap/rebase observation debt or saturate
before conversion, with a boundary test.
##########
be/src/cloud/cloud_ms_backpressure_handler.cpp:
##########
@@ -413,7 +518,8 @@ void MSBackpressureHandler::_advance_time(int ticks) {
bool MSBackpressureHandler::on_ms_busy() {
g_ms_busy_count << 1;
- if (!config::enable_ms_backpressure_handling) {
+ if (!config::enable_ms_backpressure_handling &&
+ !config::enable_ms_backpressure_handling_dry_run) {
Review Comment:
[P1] Do not enable upgrades without a downgrade driver
The handler constructor only logs when its separate tick-thread
`Thread::create()` fails, but the new default-on dry-run path still records QPS
and applies SET_LIMIT/history on MS_BUSY. That thread is the only production
caller of `_advance_time()`, so after this explicit startup failure no limit
can ever reach `on_downgrade()` or be removed; dry-run observations remain
permanently queued, and actual enforcement can retain a stale limit
indefinitely. This is distinct from the existing registry-cleanup-thread
comment: that worker owns counter eviction, while this worker owns all adaptive
recovery. Please fail construction/startup, or disable/fail MS_BUSY upgrades
unless the tick driver is live, and cover the injected failure.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]