try
This commit is contained in:
parent
843d14b7ba
commit
c938b71904
13 changed files with 698 additions and 109 deletions
1
server-rs/Cargo.lock
generated
1
server-rs/Cargo.lock
generated
|
|
@ -3901,6 +3901,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0",
|
||||
"tikv-jemalloc-sys",
|
||||
"tikv-jemallocator",
|
||||
"tokio",
|
||||
"tower",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ sentry = { version = "0.46.0", default-features = false, features = ["backtrace"
|
|||
# steady-state RSS). Decay is configured via `malloc_conf` in main.rs.
|
||||
[target.'cfg(not(target_env = "msvc"))'.dependencies]
|
||||
tikv-jemallocator = { version = "0.6", features = ["unprefixed_malloc_on_supported_platforms"] }
|
||||
# Direct mallctl access so we can force-purge dirty pages back to the OS after the
|
||||
# data load (jemalloc's idle decay/background_thread doesn't reliably return the
|
||||
# ~30GB of load-time transient buffers without subsequent allocation activity).
|
||||
tikv-jemalloc-sys = "0.6"
|
||||
|
||||
[lints.clippy]
|
||||
min_ident_chars = "warn"
|
||||
|
|
|
|||
|
|
@ -136,9 +136,74 @@ fn resident_memory_kib() -> Option<u64> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Force jemalloc to return all dirty/muzzy pages to the OS immediately.
|
||||
///
|
||||
/// jemalloc keeps freed pages "dirty" for fast reuse and only hands them back to
|
||||
/// the OS via decay. Under the bursty allocate/free of request handling (and the
|
||||
/// huge startup-load transients) that decay lags badly, so RSS balloons far above
|
||||
/// the live working set. `arena.4096.purge` (4096 == `MALLCTL_ARENAS_ALL`) purges
|
||||
/// every arena synchronously, dropping RSS back down.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn jemalloc_purge() {
|
||||
const PURGE: &[u8] = b"arena.4096.purge\0";
|
||||
// Safety: a write-only mallctl with no input/output buffers; the name is a
|
||||
// valid NUL-terminated jemalloc control string.
|
||||
unsafe {
|
||||
tikv_jemalloc_sys::mallctl(
|
||||
PURGE.as_ptr().cast(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn jemalloc_purge() {}
|
||||
|
||||
/// Enable jemalloc's background purge thread at runtime. Setting it via
|
||||
/// `background_thread:true` in `malloc_conf` is unreliable (it can be silently
|
||||
/// dropped depending on init ordering), so we also flip it explicitly here.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn enable_jemalloc_background_thread() {
|
||||
let enabled: bool = true;
|
||||
// Safety: write-only mallctl of a bool to a valid control name.
|
||||
unsafe {
|
||||
tikv_jemalloc_sys::mallctl(
|
||||
b"background_thread\0".as_ptr().cast(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::addr_of!(enabled) as *mut core::ffi::c_void,
|
||||
std::mem::size_of::<bool>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn enable_jemalloc_background_thread() {}
|
||||
|
||||
/// Periodically purge jemalloc arenas so request-handling transients are returned
|
||||
/// to the OS instead of accumulating as dirty pages. A plain OS thread (not a
|
||||
/// tokio task) keeps the madvise sweep off the async runtime.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn spawn_jemalloc_purger() {
|
||||
std::thread::Builder::new()
|
||||
.name("jemalloc-purge".to_string())
|
||||
.spawn(|| loop {
|
||||
std::thread::sleep(Duration::from_secs(10));
|
||||
jemalloc_purge();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
fn spawn_jemalloc_purger() {}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn trim_allocator(label: &'static str) {
|
||||
let before = resident_memory_kib();
|
||||
jemalloc_purge();
|
||||
let trimmed = unsafe { libc::malloc_trim(0) };
|
||||
let after = resident_memory_kib();
|
||||
if let (Some(before), Some(after)) = (before, after) {
|
||||
|
|
@ -401,6 +466,12 @@ async fn main() -> anyhow::Result<()> {
|
|||
)
|
||||
.init();
|
||||
|
||||
// Keep jemalloc from hoarding freed memory: run its background purge thread
|
||||
// and a periodic explicit purge so load-time and request-time transients are
|
||||
// returned to the OS instead of inflating RSS.
|
||||
enable_jemalloc_background_thread();
|
||||
spawn_jemalloc_purger();
|
||||
|
||||
// Initialize Prometheus metrics
|
||||
let metrics_handle = metrics::init_metrics();
|
||||
info!("Prometheus metrics initialized");
|
||||
|
|
@ -1016,17 +1087,13 @@ async fn main() -> anyhow::Result<()> {
|
|||
.layer(sentry::integrations::tower::SentryHttpLayer::new()),
|
||||
);
|
||||
|
||||
// Lock all current and future memory pages to prevent swapping
|
||||
unsafe {
|
||||
if libc::mlockall(libc::MCL_CURRENT | libc::MCL_FUTURE) != 0 {
|
||||
let err = std::io::Error::last_os_error();
|
||||
tracing::warn!(
|
||||
"mlockall failed (need CAP_IPC_LOCK or sufficient RLIMIT_MEMLOCK): {err}"
|
||||
);
|
||||
} else {
|
||||
info!("All memory pages locked (mlockall)");
|
||||
}
|
||||
}
|
||||
// NOTE: we deliberately do NOT mlockall() here. Locking MCL_CURRENT|MCL_FUTURE
|
||||
// pinned the allocator's entire mapped heap — including jemalloc's freed/dirty
|
||||
// pages — resident and non-reclaimable, inflating RSS from the ~10GB working
|
||||
// set to ~40GB and defeating the allocator's page-return entirely. The hot
|
||||
// working set stays resident naturally; freed pages are returned to the OS.
|
||||
|
||||
trim_allocator("startup complete");
|
||||
|
||||
let addr = consts::SERVER_ADDRESS;
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ fn build_style(is_dark: bool, layers: &[serde_json::Value], tile_url: &str) -> s
|
|||
|
||||
pub async fn init_tile_reader(path: &std::path::Path) -> anyhow::Result<TileReader> {
|
||||
let backend = FileBackend::open(path)?;
|
||||
let reader = AsyncPmTilesReader::try_from_cached_source(backend, HashMapCache::default()).await?;
|
||||
let reader =
|
||||
AsyncPmTilesReader::try_from_cached_source(backend, HashMapCache::default()).await?;
|
||||
Ok(reader)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue