mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
perf(gateway): scale request hot paths for 20k streams
Shard and singleflight hot-path caches, batch and prioritize candidate and usage lifecycle persistence, and extend database and pressure-test instrumentation for 20k concurrent streams.
This commit is contained in:
@@ -5,6 +5,11 @@ const DEFAULT_USAGE_BACKGROUND_RUNTIME_THREADS: usize = 8;
|
||||
const MAX_USAGE_BACKGROUND_RUNTIME_THREADS: usize = 64;
|
||||
const GATEWAY_USAGE_BACKGROUND_RUNTIME_THREADS_ENV: &str = "AETHER_GATEWAY_USAGE_RUNTIME_THREADS";
|
||||
const USAGE_BACKGROUND_RUNTIME_THREADS_ENV: &str = "AETHER_USAGE_RUNTIME_THREADS";
|
||||
const DEFAULT_USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS: usize = 16;
|
||||
const MAX_USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS: usize = 128;
|
||||
const GATEWAY_USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS_ENV: &str =
|
||||
"AETHER_GATEWAY_USAGE_RUNTIME_BLOCKING_THREADS";
|
||||
const USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS_ENV: &str = "AETHER_USAGE_RUNTIME_BLOCKING_THREADS";
|
||||
const USAGE_BACKGROUND_RUNTIME_STACK_BYTES: usize = 8 * 1024 * 1024;
|
||||
const USAGE_BACKGROUND_RUNTIME_THREAD_NAME: &str = "aether-usage-runtime";
|
||||
|
||||
@@ -20,9 +25,13 @@ fn usage_background_runtime() -> &'static tokio::runtime::Runtime {
|
||||
static RUNTIME: OnceLock<&'static tokio::runtime::Runtime> = OnceLock::new();
|
||||
|
||||
RUNTIME.get_or_init(|| {
|
||||
let worker_threads = usage_background_runtime_threads();
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.worker_threads(usage_background_runtime_threads())
|
||||
.worker_threads(worker_threads)
|
||||
// Usage event builders use spawn_blocking; keep bursts from creating the Tokio
|
||||
// default pool of up to 512 threads and consuming an unbounded stack budget.
|
||||
.max_blocking_threads(usage_background_runtime_blocking_threads())
|
||||
.thread_name(USAGE_BACKGROUND_RUNTIME_THREAD_NAME)
|
||||
.thread_stack_size(USAGE_BACKGROUND_RUNTIME_STACK_BYTES)
|
||||
.build()
|
||||
@@ -40,6 +49,15 @@ fn usage_background_runtime_threads() -> usize {
|
||||
)
|
||||
}
|
||||
|
||||
fn usage_background_runtime_blocking_threads() -> usize {
|
||||
parse_usage_background_runtime_blocking_threads(
|
||||
std::env::var(GATEWAY_USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS_ENV)
|
||||
.ok()
|
||||
.or_else(|| std::env::var(USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS_ENV).ok())
|
||||
.as_deref(),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_usage_background_runtime_threads(value: Option<&str>) -> usize {
|
||||
value
|
||||
.and_then(|value| value.trim().parse::<usize>().ok())
|
||||
@@ -48,9 +66,24 @@ fn parse_usage_background_runtime_threads(value: Option<&str>) -> usize {
|
||||
.clamp(1, MAX_USAGE_BACKGROUND_RUNTIME_THREADS)
|
||||
}
|
||||
|
||||
fn parse_usage_background_runtime_blocking_threads(value: Option<&str>) -> usize {
|
||||
value
|
||||
.and_then(|value| value.trim().parse::<usize>().ok())
|
||||
.filter(|threads| *threads > 0)
|
||||
.unwrap_or(DEFAULT_USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS)
|
||||
.clamp(1, MAX_USAGE_BACKGROUND_RUNTIME_BLOCKING_THREADS)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{parse_usage_background_runtime_threads, spawn_on_usage_background_runtime};
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::{
|
||||
parse_usage_background_runtime_blocking_threads, parse_usage_background_runtime_threads,
|
||||
spawn_on_usage_background_runtime, usage_background_runtime_blocking_threads,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_background_runtime_runs_on_dedicated_named_threads() {
|
||||
@@ -77,4 +110,79 @@ mod tests {
|
||||
);
|
||||
assert_eq!(parse_usage_background_runtime_threads(Some("999")), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_background_runtime_blocking_threads_are_bounded() {
|
||||
assert_eq!(parse_usage_background_runtime_blocking_threads(None), 16);
|
||||
assert_eq!(
|
||||
parse_usage_background_runtime_blocking_threads(Some("0")),
|
||||
16
|
||||
);
|
||||
assert_eq!(
|
||||
parse_usage_background_runtime_blocking_threads(Some("32")),
|
||||
32
|
||||
);
|
||||
assert_eq!(
|
||||
parse_usage_background_runtime_blocking_threads(Some("999")),
|
||||
128
|
||||
);
|
||||
assert_eq!(
|
||||
parse_usage_background_runtime_blocking_threads(Some("not-a-number")),
|
||||
16
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn usage_background_runtime_does_not_exceed_blocking_thread_budget() {
|
||||
let configured_limit = usage_background_runtime_blocking_threads();
|
||||
let submissions = configured_limit.saturating_add(2).min(20);
|
||||
let expected_started = submissions.min(configured_limit);
|
||||
let active = Arc::new(AtomicUsize::new(0));
|
||||
let max_active = Arc::new(AtomicUsize::new(0));
|
||||
let started = Arc::new(AtomicUsize::new(0));
|
||||
let release = Arc::new(AtomicBool::new(false));
|
||||
let mut handles = Vec::with_capacity(submissions);
|
||||
|
||||
for _ in 0..submissions {
|
||||
let active = Arc::clone(&active);
|
||||
let max_active = Arc::clone(&max_active);
|
||||
let started = Arc::clone(&started);
|
||||
let release = Arc::clone(&release);
|
||||
handles.push(spawn_on_usage_background_runtime(async move {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let current = active.fetch_add(1, Ordering::AcqRel) + 1;
|
||||
max_active.fetch_max(current, Ordering::AcqRel);
|
||||
started.fetch_add(1, Ordering::AcqRel);
|
||||
while !release.load(Ordering::Acquire) {
|
||||
std::thread::park_timeout(Duration::from_millis(1));
|
||||
}
|
||||
active.fetch_sub(1, Ordering::AcqRel);
|
||||
})
|
||||
.await
|
||||
.expect("bounded blocking task should complete");
|
||||
}));
|
||||
}
|
||||
|
||||
tokio::time::timeout(Duration::from_secs(5), async {
|
||||
while started.load(Ordering::Acquire) < expected_started {
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
}
|
||||
})
|
||||
.await
|
||||
.expect("blocking pool should start tasks within the budget");
|
||||
|
||||
assert!(
|
||||
max_active.load(Ordering::Acquire) <= configured_limit,
|
||||
"blocking pool exceeded configured limit: max={} limit={configured_limit}",
|
||||
max_active.load(Ordering::Acquire)
|
||||
);
|
||||
|
||||
release.store(true, Ordering::Release);
|
||||
for handle in handles {
|
||||
tokio::time::timeout(Duration::from_secs(5), handle)
|
||||
.await
|
||||
.expect("blocking task should stop after release")
|
||||
.expect("blocking task should not panic");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user