perf(ssr): optimize request throughput by 32%
- Cache ammonia::Builder with LazyLock (was rebuilt per request) - Enable tracing release_max_level_info to strip tracing overhead at compile time - Remove TraceLayer and tower-http trace feature from production - Increase DB pool size 10→20 (configurable via DB_POOL_SIZE) - Increase SSR cache TTL 300s→3600s (configurable via SSR_CACHE_SECS) Benchmark: 7,444 → 9,840 req/s, P99 latency 27.6ms → 11.1ms
This commit is contained in:
parent
8dbe564ca2
commit
bd9e87128d
@ -19,3 +19,9 @@ WEBP_METHOD=2
|
|||||||
|
|
||||||
# Maximum concurrent sessions per user (default: 5, minimum: 1)
|
# Maximum concurrent sessions per user (default: 5, minimum: 1)
|
||||||
MAX_SESSIONS_PER_USER=5
|
MAX_SESSIONS_PER_USER=5
|
||||||
|
|
||||||
|
# Database connection pool size (default: 20)
|
||||||
|
DB_POOL_SIZE=20
|
||||||
|
|
||||||
|
# SSR page cache duration in seconds (default: 3600)
|
||||||
|
SSR_CACHE_SECS=3600
|
||||||
|
|||||||
@ -41,6 +41,8 @@ RATE_LIMIT_UPLOAD_PER_SEC=2
|
|||||||
RATE_LIMIT_UPLOAD_BURST=15
|
RATE_LIMIT_UPLOAD_BURST=15
|
||||||
RATE_LIMIT_IMAGE_PER_SEC=10
|
RATE_LIMIT_IMAGE_PER_SEC=10
|
||||||
RATE_LIMIT_IMAGE_BURST=50
|
RATE_LIMIT_IMAGE_BURST=50
|
||||||
|
DB_POOL_SIZE=20 # database connection pool size
|
||||||
|
SSR_CACHE_SECS=3600 # incremental SSR cache TTL
|
||||||
```
|
```
|
||||||
|
|
||||||
Run migrations before first dev server start:
|
Run migrations before first dev server start:
|
||||||
|
|||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4379,7 +4379,6 @@ dependencies = [
|
|||||||
"tower",
|
"tower",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -15,9 +15,9 @@ chrono = { version = "0.4", features = ["serde"] }
|
|||||||
regex = "1.12"
|
regex = "1.12"
|
||||||
pulldown-cmark = "0.13"
|
pulldown-cmark = "0.13"
|
||||||
dotenvy = { version = "0.15", optional = true }
|
dotenvy = { version = "0.15", optional = true }
|
||||||
tracing = { version = "0.1", optional = true }
|
tracing = { version = "0.1", optional = true, features = ["release_max_level_info"] }
|
||||||
tracing-subscriber = { version = "0.3", optional = true }
|
tracing-subscriber = { version = "0.3", optional = true }
|
||||||
tower-http = { version = "0.6", features = ["trace"], optional = true }
|
tower-http = { version = "0.6", optional = true }
|
||||||
rand = { version = "0.8", features = ["getrandom"] }
|
rand = { version = "0.8", features = ["getrandom"] }
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
http = "1"
|
http = "1"
|
||||||
|
|||||||
@ -9,7 +9,7 @@ fn html_escape(s: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub fn clean_comment_html(input: &str) -> String {
|
static COMMENT_AMMONIA_BUILDER: std::sync::LazyLock<ammonia::Builder> = std::sync::LazyLock::new(|| {
|
||||||
let mut builder = ammonia::Builder::default();
|
let mut builder = ammonia::Builder::default();
|
||||||
builder
|
builder
|
||||||
.rm_tags(["img", "details", "summary"])
|
.rm_tags(["img", "details", "summary"])
|
||||||
@ -25,8 +25,12 @@ pub fn clean_comment_html(input: &str) -> String {
|
|||||||
.add_tag_attributes("a", &["class", "aria-hidden", "aria-label"])
|
.add_tag_attributes("a", &["class", "aria-hidden", "aria-label"])
|
||||||
.add_tag_attributes("span", &["class"])
|
.add_tag_attributes("span", &["class"])
|
||||||
.link_rel(Some("nofollow noopener"));
|
.link_rel(Some("nofollow noopener"));
|
||||||
|
builder
|
||||||
|
});
|
||||||
|
|
||||||
builder.clean(input).to_string()
|
#[cfg(feature = "server")]
|
||||||
|
pub fn clean_comment_html(input: &str) -> String {
|
||||||
|
COMMENT_AMMONIA_BUILDER.clean(input).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#![allow(clippy::unused_unit, deprecated, unused_imports)]
|
#![allow(clippy::unused_unit, deprecated, unused_imports)]
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub fn clean_html(input: &str) -> String {
|
static AMMONIA_BUILDER: std::sync::LazyLock<ammonia::Builder> = std::sync::LazyLock::new(|| {
|
||||||
let mut builder = ammonia::Builder::default();
|
let mut builder = ammonia::Builder::default();
|
||||||
builder
|
builder
|
||||||
.add_generic_attributes(&[
|
.add_generic_attributes(&[
|
||||||
@ -24,8 +24,12 @@ pub fn clean_html(input: &str) -> String {
|
|||||||
.add_tag_attributes("h4", &["id", "class"])
|
.add_tag_attributes("h4", &["id", "class"])
|
||||||
.add_tag_attributes("h5", &["id", "class"])
|
.add_tag_attributes("h5", &["id", "class"])
|
||||||
.add_tag_attributes("h6", &["id", "class"]);
|
.add_tag_attributes("h6", &["id", "class"]);
|
||||||
|
builder
|
||||||
|
});
|
||||||
|
|
||||||
builder.clean(input).to_string()
|
#[cfg(feature = "server")]
|
||||||
|
pub fn clean_html(input: &str) -> String {
|
||||||
|
AMMONIA_BUILDER.clean(input).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
@ -16,7 +16,10 @@ pub static DB_POOL: LazyLock<Pool> = LazyLock::new(|| {
|
|||||||
let mgr = Manager::from_config(pg_cfg, NoTls, mgr_cfg);
|
let mgr = Manager::from_config(pg_cfg, NoTls, mgr_cfg);
|
||||||
|
|
||||||
Pool::builder(mgr)
|
Pool::builder(mgr)
|
||||||
.max_size(10)
|
.max_size(std::env::var("DB_POOL_SIZE")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(20))
|
||||||
.build()
|
.build()
|
||||||
.expect("Failed to create database connection pool")
|
.expect("Failed to create database connection pool")
|
||||||
});
|
});
|
||||||
|
|||||||
11
src/main.rs
11
src/main.rs
@ -34,7 +34,6 @@ fn main() {
|
|||||||
|
|
||||||
dioxus::server::serve(|| async move {
|
dioxus::server::serve(|| async move {
|
||||||
use dioxus::server::{axum, DioxusRouterExt, ServeConfig};
|
use dioxus::server::{axum, DioxusRouterExt, ServeConfig};
|
||||||
use tower_http::trace::TraceLayer;
|
|
||||||
|
|
||||||
tokio::spawn(async {
|
tokio::spawn(async {
|
||||||
tasks::ip_purge::run_purge().await;
|
tasks::ip_purge::run_purge().await;
|
||||||
@ -46,7 +45,12 @@ fn main() {
|
|||||||
|
|
||||||
let config = ServeConfig::builder().incremental(
|
let config = ServeConfig::builder().incremental(
|
||||||
dioxus::server::IncrementalRendererConfig::default()
|
dioxus::server::IncrementalRendererConfig::default()
|
||||||
.invalidate_after(std::time::Duration::from_secs(300)),
|
.invalidate_after(std::time::Duration::from_secs(
|
||||||
|
std::env::var("SSR_CACHE_SECS")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse().ok())
|
||||||
|
.unwrap_or(3600),
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
let api_routes = axum::Router::new().route(
|
let api_routes = axum::Router::new().route(
|
||||||
"/api/upload",
|
"/api/upload",
|
||||||
@ -64,8 +68,7 @@ fn main() {
|
|||||||
|
|
||||||
let router = api_routes
|
let router = api_routes
|
||||||
.merge(static_routes)
|
.merge(static_routes)
|
||||||
.merge(dioxus_app)
|
.merge(dioxus_app);
|
||||||
.layer(TraceLayer::new_for_http());
|
|
||||||
|
|
||||||
Ok(router)
|
Ok(router)
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user