Skip to content

Commit 5ae6d12

Browse files
authored
make grpc subscriber count configurable (#27293)
## Description Making the subscriber limit configurable (still defaults to 1024). I plan to do some load testing and I expect to be able to go beyond this.
1 parent ebb83f4 commit 5ae6d12

5 files changed

Lines changed: 30 additions & 17 deletions

File tree

crates/sui-config/src/rpc_config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ pub struct RpcConfig {
6969
#[serde(skip_serializing_if = "Option::is_none")]
7070
pub subscription_watermark_interval: Option<u32>,
7171

72+
/// Maximum number of concurrent RPC subscriptions. Defaults to 1024.
73+
#[serde(skip_serializing_if = "Option::is_none")]
74+
pub subscription_max_subscribers: Option<usize>,
75+
7276
/// Number of parallel shard tasks that evaluate subscription filters and
7377
/// deliver updates. Each subscriber lives on one shard; per-checkpoint
7478
/// filter evaluation parallelizes across shards. Defaults to the host's

crates/sui-fork/src/startup.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ pub async fn initialize(
163163
// is wired into `RpcService` in `run` so subscribers can register.
164164
let registry = Registry::new();
165165
let (checkpoint_sender, subscription_handle) =
166-
SubscriptionService::build(&registry, None, None, None);
166+
SubscriptionService::build(&registry, None, None, None, None);
167167

168168
Ok((
169169
Context::new(simulacrum, chain_identifier, checkpoint_sender),

crates/sui-fork/src/tests/subscription_e2e.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ impl ServerHarness {
8282

8383
let registry = Registry::new();
8484
let (checkpoint_sender, subscription_handle) =
85-
SubscriptionService::build(&registry, None, None, None);
85+
SubscriptionService::build(&registry, None, None, None, None);
8686

8787
let context = Arc::new(Context::new(sim, Chain::Unknown, checkpoint_sender));
8888

crates/sui-node/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2794,12 +2794,17 @@ async fn build_http_servers(
27942794
.rpc
27952795
.as_ref()
27962796
.and_then(|rpc| rpc.subscription_watermark_interval);
2797+
let subscription_max_subscribers = config
2798+
.rpc
2799+
.as_ref()
2800+
.and_then(|rpc| rpc.subscription_max_subscribers);
27972801
let subscription_shards = config.rpc.as_ref().and_then(|rpc| rpc.subscription_shards);
27982802
let (subscription_service_checkpoint_sender, subscription_service_handle) =
27992803
SubscriptionService::build(
28002804
prometheus_registry,
28012805
indexed_checkpoint,
28022806
subscription_watermark_interval,
2807+
subscription_max_subscribers,
28032808
subscription_shards,
28042809
);
28052810
let rpc_router = {

crates/sui-rpc-api/src/subscription/mod.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ mod matcher;
2222
const CHECKPOINT_MAILBOX_SIZE: usize = 1024;
2323
const MAILBOX_SIZE: usize = 128;
2424
const SUBSCRIPTION_CHANNEL_SIZE: usize = 256;
25-
const MAX_SUBSCRIBERS: usize = 1024;
25+
const DEFAULT_MAX_SUBSCRIBERS: usize = 1024;
2626
/// Bound on each shard task's mailbox (registrations, checkpoint fan-out,
2727
/// and lag teardowns from the dispatcher).
2828
const SHARD_MAILBOX_SIZE: usize = 64;
@@ -295,6 +295,8 @@ pub struct SubscriptionService {
295295
/// Filtered-subscriber counts per key space, shared with the shards;
296296
/// gates per-checkpoint key extraction.
297297
counters: Arc<SubscriberCounts>,
298+
/// Global admission limit across all shards.
299+
max_subscribers: usize,
298300

299301
// When set, delivery of a checkpoint waits until the index has committed
300302
// it (see [`IndexedCheckpointFn`]). `None` preserves the immediate-delivery
@@ -305,12 +307,14 @@ pub struct SubscriptionService {
305307
}
306308

307309
impl SubscriptionService {
308-
/// `None` defaults `watermark_interval` to 25 checkpoints and `shards` to
309-
/// the host's available parallelism, with a minimum of one.
310+
/// `None` defaults `watermark_interval` to 25 checkpoints,
311+
/// `max_subscribers` to 1024, and `shards` to the host's available
312+
/// parallelism, with a minimum of one shard.
310313
pub fn build(
311314
registry: &prometheus::Registry,
312315
indexed_checkpoint: Option<IndexedCheckpointFn>,
313316
watermark_interval: Option<u32>,
317+
max_subscribers: Option<usize>,
314318
shards: Option<u32>,
315319
) -> (
316320
broadcast::Sender<Arc<Checkpoint>>,
@@ -321,6 +325,7 @@ impl SubscriptionService {
321325
let (subscription_request_sender, mailbox) = mpsc::channel(MAILBOX_SIZE);
322326

323327
let counters = Arc::new(SubscriberCounts::default());
328+
let max_subscribers = max_subscribers.unwrap_or(DEFAULT_MAX_SUBSCRIBERS);
324329
let watermark_interval = watermark_interval
325330
.unwrap_or(DEFAULT_WATERMARK_INTERVAL)
326331
.max(1);
@@ -348,6 +353,7 @@ impl SubscriptionService {
348353
shards: shard_senders,
349354
next_shard: 0,
350355
counters,
356+
max_subscribers,
351357
indexed_checkpoint,
352358
metrics,
353359
}
@@ -510,10 +516,10 @@ impl SubscriptionService {
510516
// can have at one time. `counters.total` is incremented here at
511517
// admission and decremented by the shards on departure/clear, so it
512518
// counts live + in-flight subscribers across every shard.
513-
if self.counters.total.load(Ordering::Relaxed) >= MAX_SUBSCRIBERS {
519+
if self.counters.total.load(Ordering::Relaxed) >= self.max_subscribers {
514520
trace!(
515521
"failed to register new subscriber: hit maximum number of subscribers {}",
516-
MAX_SUBSCRIBERS
522+
self.max_subscribers
517523
);
518524
// Dropping the oneshot makes `register_subscription` return
519525
// `None` -> `Status::unavailable`.
@@ -607,6 +613,7 @@ mod tests {
607613
shards: shard_senders,
608614
next_shard: 0,
609615
counters,
616+
max_subscribers: DEFAULT_MAX_SUBSCRIBERS,
610617
indexed_checkpoint,
611618
metrics,
612619
};
@@ -902,26 +909,23 @@ mod tests {
902909
}
903910

904911
#[tokio::test]
905-
async fn cap_is_enforced_globally_across_shards() {
912+
async fn configured_cap_is_enforced_globally_across_shards() {
906913
let (mut service, mut shards) = test_service(2);
907-
let mut receivers = Vec::with_capacity(MAX_SUBSCRIBERS);
908-
for i in 0..MAX_SUBSCRIBERS {
909-
// Keep each 64-slot shard mailbox from filling: the dispatcher's
910-
// bounded send would otherwise block with no spawned shard task.
911-
if i % 32 == 0 {
912-
drain(&mut shards);
913-
}
914+
let max_subscribers = 3;
915+
service.max_subscribers = max_subscribers;
916+
let mut receivers = Vec::with_capacity(max_subscribers);
917+
for _ in 0..max_subscribers {
914918
receivers.push(register(&mut service, unfiltered()).await.unwrap());
915919
}
916920
drain(&mut shards);
917921
assert_eq!(
918922
service.counters.total.load(Ordering::Relaxed),
919-
MAX_SUBSCRIBERS
923+
max_subscribers
920924
);
921925
// The gauge mirrors the admission count for observability.
922926
assert_eq!(
923927
service.metrics.inflight_subscribers.get(),
924-
MAX_SUBSCRIBERS as i64
928+
max_subscribers as i64
925929
);
926930
assert!(!shards[0].matcher.is_empty());
927931
assert!(!shards[1].matcher.is_empty());

0 commit comments

Comments
 (0)