From a01a29a55df1078fee52363a11416a2a2fcdb460 Mon Sep 17 00:00:00 2001 From: nashzhou Date: Wed, 21 Oct 2026 10:35:49 +0800 Subject: [PATCH] feat(config): add per-component pool watermark overrides [pool.network] and [pool.block] gain optional low_watermark / high_watermark overrides; unset values fall back to the shared top-level [pool] watermarks. The ublk block pool needs a different shape from the process pools: its high watermark is a strict idle cap (maintenance is disabled), so with a large sandbox fleet the shared cap forces released devices through STOP_DEV + DEL_DEV and re-creation on every pause/resume wave. Decoupling lets operators size the block pool above the largest transient idle-device inventory (about two devices per concurrently pausing sandbox) so devices are recycled instead of destroyed, while network and firecracker pools keep the shared values. Set [pool.block] low_watermark = 0 together with startup_prewarm = false to run the pool purely release-fed: devices are only reused, never prewarmed or deleted. --- config/default.toml | 6 +++ docs/src/configuration/reference.md | 4 ++ src/cfg.rs | 60 +++++++++++++++++++++++++++-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/config/default.toml b/config/default.toml index 122f76f..3cd3942 100644 --- a/config/default.toml +++ b/config/default.toml @@ -344,6 +344,12 @@ enabled = true # Omit to prewarm only when the kernel supports UBLK_F_UPDATE_SIZE. Set an # explicit boolean to override capability-based selection. # startup_prewarm = true +# Per-component watermark overrides; unset falls back to the [pool] values. +# Size high above the largest transient idle-device inventory (about two +# devices per concurrently pausing sandbox) so released devices are reused +# instead of stopped. +# low_watermark = 0 +# high_watermark = 2048 [pool.firecracker] # Enable pre-spawned Firecracker processes for snapshot resume. diff --git a/docs/src/configuration/reference.md b/docs/src/configuration/reference.md index 6da9df4..ff0219f 100644 --- a/docs/src/configuration/reference.md +++ b/docs/src/configuration/reference.md @@ -308,8 +308,12 @@ Component sections: | Section | Key | Type | Default | Description | |---------|-----|------|---------|-------------| +| `[pool.network]` | `low_watermark` | integer | top-level `[pool]` | Per-component low watermark override for the network-slot pool | +| `[pool.network]` | `high_watermark` | integer | top-level `[pool]` | Per-component high watermark override for the network-slot pool | | `[pool.network]` | `maintenance_enabled` | boolean | `true` | Enable the background network-slot maintenance worker | | `[pool.block]` | `enabled` | boolean | `true` | Enable the ublk overlaybd warm-device pool | +| `[pool.block]` | `low_watermark` | integer | top-level `[pool]` | Per-component low watermark override for the ublk device pool. Set `0` to disable warm-capacity refill while keeping release-fed reuse | +| `[pool.block]` | `high_watermark` | integer | top-level `[pool]` | Per-component high watermark override for the ublk device pool. Size it above the largest transient idle-device inventory (about two devices per concurrently pausing sandbox) so released devices are reused instead of stopped | | `[pool.block]` | `startup_prewarm` | boolean | capability-based | Prewarm block devices after the first reusable image shape is known. When omitted, it is enabled only if the kernel supports `UBLK_F_UPDATE_SIZE`; an explicit value overrides detection | | `[pool.firecracker]` | `enabled` | boolean | `true` | Enable pre-spawned Firecracker processes for snapshot resume | | `[pool.firecracker]` | `maintenance_enabled` | boolean | `true` | Enable the background Firecracker process maintenance worker | diff --git a/src/cfg.rs b/src/cfg.rs index 6e755dc..84f161f 100644 --- a/src/cfg.rs +++ b/src/cfg.rs @@ -218,6 +218,12 @@ pub struct PoolComponentConfig { pub maintenance_enabled: bool, #[config(default = true)] pub startup_prewarm: bool, + /// Per-component low watermark override; falls back to the top-level + /// `[pool] low_watermark` when unset. + pub low_watermark: Option, + /// Per-component high watermark override; falls back to the top-level + /// `[pool] high_watermark` when unset. + pub high_watermark: Option, } #[derive(Debug, Config, Clone)] @@ -887,8 +893,8 @@ impl AppConfig { let pool = &self.pool.network; warm_pool::PoolConfig { - low_watermark: self.pool.low_watermark, - high_watermark: self.pool.high_watermark, + low_watermark: pool.low_watermark.unwrap_or(self.pool.low_watermark), + high_watermark: pool.high_watermark.unwrap_or(self.pool.high_watermark), maintenance_enabled: pool.enabled && pool.maintenance_enabled, startup_prewarm: pool.startup_prewarm, } @@ -902,8 +908,8 @@ impl AppConfig { } Some(warm_pool::PoolConfig { - low_watermark: self.pool.low_watermark, - high_watermark: self.pool.high_watermark, + low_watermark: pool.low_watermark.unwrap_or(self.pool.low_watermark), + high_watermark: pool.high_watermark.unwrap_or(self.pool.high_watermark), // The ublk daemon uses async request-time refill because the // reusable device shape is image/size dependent. maintenance_enabled: false, @@ -2200,6 +2206,52 @@ mod tests { assert!(config.validate_pool_config().is_ok()); } + #[test] + fn component_pool_config_honors_watermark_overrides() { + let config = AppConfig { + pool: PoolTomlConfig { + low_watermark: 2, + high_watermark: 64, + network: PoolComponentConfig { + low_watermark: Some(4), + high_watermark: Some(128), + ..PoolComponentConfig::default() + }, + block: PoolComponentConfig { + low_watermark: Some(0), + high_watermark: Some(2048), + ..PoolComponentConfig::default() + }, + ..PoolTomlConfig::default() + }, + ..AppConfig::default() + }; + + let network = config.network_pool_config(); + assert_eq!(network.low_watermark, 4); + assert_eq!(network.high_watermark, 128); + + let block = config.block_pool_config().expect("block pool enabled"); + assert_eq!(block.low_watermark, 0); + assert_eq!(block.high_watermark, 2048); + } + + #[test] + fn component_pool_config_falls_back_to_top_level_watermarks() { + let config = AppConfig { + pool: PoolTomlConfig { + low_watermark: 2, + high_watermark: 64, + ..PoolTomlConfig::default() + }, + ..AppConfig::default() + }; + + let block = config.block_pool_config().expect("block pool enabled"); + assert_eq!(block.low_watermark, 2); + assert_eq!(block.high_watermark, 64); + } + #[test] fn firecracker_pool_fill_concurrency_rejects_zero() { let config = AppConfig { -- 2.43.0