feat(info): report command statistics per namespace#3557
Conversation
Track command calls, latency, and histograms per namespace and report them in INFO (stats, commandstats) and LATENCY HISTOGRAM scoped to the caller's namespace; the admin/default namespace sees the aggregate across all of them. Closes apache#755 Assisted-by: Claude Code Signed-off-by: nhancdt <nhancu2602@gmail.com>
|
@PragmaTwice I'd love to hear your thought about this change. Especially, some stats still stay global in this PR for the sack of simplicity. |
|
@jihuayu Thanks for running the CI pipeline. The only failing test is: This seems unrelated to the PR change. (as it was reported as flaky test in #3515). Could you kindly re-run the pipeline again? |
|
There was a problem hiding this comment.
Pull request overview
This PR adds per-namespace command statistics so that INFO (Stats/CommandStats) and LATENCY HISTOGRAM report metrics scoped to the caller’s namespace, while the default/admin namespace view returns an aggregate across all namespaces.
Changes:
- Introduces a per-namespace
Statsmap inServer, with lazy creation and aggregation for the default/admin namespace view. - Records command call/latency stats into a connection-cached per-namespace
Statson the command execution hot path. - Adds a Go integration test validating namespace scoping, aggregation, and namespace deletion behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/gocase/unit/info/info_test.go | Adds TestNamespaceStats to validate INFO/LATENCY stats scoping and aggregation semantics. |
| src/server/server.h | Extends Server APIs to accept namespace for Stats/CommandStats and adds per-namespace Stats storage/aggregation helpers. |
| src/server/server.cc | Implements per-namespace Stats creation, aggregation, and updates INFO sections to use namespace-aware sources. |
| src/server/redis_connection.h | Adds cached per-namespace Stats pointer on the connection object. |
| src/server/redis_connection.cc | Switches command stats recording to the connection’s cached per-namespace Stats. |
| src/commands/cmd_server.cc | Clears namespace stats on NAMESPACE DEL and makes LATENCY HISTOGRAM use namespace-aware histograms/aggregate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::unique_lock<std::shared_mutex> lock(ns_stats_mu_); | ||
| if (auto it = ns_stats_.find(ns); it != ns_stats_.end()) { | ||
| return it->second; | ||
| } | ||
| auto ns_stats = std::make_shared<Stats>(config_->histogram_bucket_boundaries); | ||
| initCommandStats(ns_stats.get()); | ||
| ns_stats_[ns] = ns_stats; | ||
| return ns_stats; |
There was a problem hiding this comment.
histogram_bucket_boundaries is a read-only config, so it can't be changed at runtime.
| for (const auto &[cmd, hist] : ns_stats->commands_histogram) { | ||
| auto &agg_hist = agg->commands_histogram[cmd]; | ||
| agg_hist.calls.fetch_add(hist.calls.load(), std::memory_order_relaxed); | ||
| agg_hist.sum.fetch_add(hist.sum.load(), std::memory_order_relaxed); | ||
| for (std::size_t i = 0; i < hist.buckets.size(); ++i) { | ||
| agg_hist.buckets[i]->fetch_add(hist.buckets[i]->load(), std::memory_order_relaxed); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
histogram_bucket_boundaries is a read-only config, so it can't be changed at runtime.
| void Server::recordInstantaneousMetrics() { | ||
| auto rocksdb_stats = storage->GetDB()->GetDBOptions().statistics; | ||
| stats.TrackInstantaneousMetric(STATS_METRIC_COMMAND, stats.total_calls); | ||
| // Sample each namespace's command metric, and feed the sum into the global metric so the | ||
| // admin/default view reports aggregate ops/sec without keeping a global command counter on the hot path. | ||
| uint64_t total_calls = 0; | ||
| { | ||
| std::shared_lock<std::shared_mutex> lock(ns_stats_mu_); | ||
| for (const auto &[ns, ns_stats] : ns_stats_) { | ||
| auto calls = ns_stats->total_calls.load(); | ||
| ns_stats->TrackInstantaneousMetric(STATS_METRIC_COMMAND, calls); | ||
| total_calls += calls; | ||
| } | ||
| } | ||
| stats.TrackInstantaneousMetric(STATS_METRIC_COMMAND, total_calls); |
There was a problem hiding this comment.
As this method is not called too frequently, I believe the overhead is acceptable.
| Server::InfoEntries Server::GetStatsInfo(const std::string &ns) { | ||
| // Command stats are per namespace; the admin/default namespace sees the aggregate across all of them. | ||
| auto cmd_stats_ptr = ns == kDefaultNamespace ? AggregateNamespaceStats() : GetOrCreateNamespaceStats(ns); | ||
| const Stats &cmd_stats = *cmd_stats_ptr; | ||
|
|
||
| Server::InfoEntries entries; | ||
| entries.emplace_back("total_connections_received", total_clients_.load()); | ||
| entries.emplace_back("total_commands_processed", stats.total_calls.load()); | ||
| entries.emplace_back("instantaneous_ops_per_sec", stats.GetInstantaneousMetric(STATS_METRIC_COMMAND)); | ||
| entries.emplace_back("total_commands_processed", cmd_stats.total_calls.load()); | ||
| // Per-namespace ops/sec comes from the namespace's own sampled metric; the admin/default view uses | ||
| // the global metric, which the sampler feeds with the sum across all namespaces. | ||
| auto ops_per_sec = ns == kDefaultNamespace ? stats.GetInstantaneousMetric(STATS_METRIC_COMMAND) | ||
| : cmd_stats.GetInstantaneousMetric(STATS_METRIC_COMMAND); | ||
| entries.emplace_back("instantaneous_ops_per_sec", ops_per_sec); |
There was a problem hiding this comment.
As the aggregation logic is fast, I believe we can ignore the concern here.
jihuayu
left a comment
There was a problem hiding this comment.
Hello @nhancdt2602.Thank you for your contribution. I think this PR still has a few issues.
- latency command is admin only. It will make the namespace user could not get it's latency.
kvrocks/src/commands/cmd_server.cc
Line 1827 in 634ed15
- Deleting a namespace does not disconnect existing connections. This causes the old connections’
ns_stats_references to diverge.
After authentication:
ns_stats_["ns1"] -> Stats A
Active connection -> Stats A
After deleting `ns1`:
ns_stats_ no longer contains ns1
Active connection -> Stats A // kept alive by shared_ptr
After the old connection executes `INFO`:
ns_stats_["ns1"] -> newly created Stats B
Active connection -> Stats A
- Deleting a namespace may cause an underflow here.
Lines 107 to 117 in 8c26549
|
@jihuayu Thanks for your reply. To fix points 2 and 3, can I keep the stats entry in the namespace map even after the namespace is deleted? This would be consistent with the current behavior of |
|
@jihuayu Regarding point 1, I’m still not sure I understand your concern. Are you suggesting that we should not expose latency and histogram metrics for namespace-scoped connections? |
|
@nhancdt2602 Sorry, I’m late.
I have no objection to that, since deleting a namespace is a low-frequency operation. What I meant is that this command should not be restricted to ADMIN users. Namespace users are not ADMIN users, so they would be unable to retrieve latency information. |


Summary
This change tracks command calls, latency, and histograms per namespace. A namespaced connection sees its own numbers in INFO (Stats, CommandStats) and LATENCY HISTOGRAM; the admin/default namespace sees the aggregate across all namespaces.
Behavior:
INFO commandstats-> returns stats scoped by ns1 onlyINFO commandstats-> returns stats aggregated from all namespacesCloses #755.
Changes
ns_stats_field in the Server class (guarded by a shared_mutex), created lazily viaGetOrCreateNamespaceStats.GetStatsInfo,GetCommandsStatsInfo,LATENCY HISTOGRAMresolve their source by caller namespace, orAggregateNamespaceStats()for the admin/default view.Implementation notes
Test
unit/namespacemoduleunit/infomoduleAI disclosure