From 5629ba6200dde6340624add1edbff7bf2a624491 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 22 Jun 2026 08:46:24 +0000 Subject: [PATCH] perf(pools): size image/rayon pools to the CFS quota (effective_parallelism) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The thumbnail decode semaphore, the transcode rayon pool, and the ffmpeg video-thumbnail fan-out all sized from std::thread::available_parallelism(), which honours CPU affinity but ignores the CFS bandwidth quota (--cpus / cgroup cpu.max). Under a container quota they therefore permit one CPU-heavy task per *host* core onto cores the scheduler can't grant — the same over-subscription the runtime worker pool had. Switch all three to common::runtime::effective_parallelism() (= min(affinity, CFS quota)), and fix the doc comments that wrongly claimed available_parallelism respects cgroup quotas. No change off-quota (effective == available there); the env overrides (OXICLOUD_THUMBNAIL_DECODE_CONCURRENCY, OXICLOUD_VIDEO_THUMBNAIL_CONCURRENCY) are unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01JG5yYZ9s868mJwqT2Qz7ez --- src/common/di.rs | 6 +++--- .../services/image_transcode_service.rs | 13 +++++++------ src/infrastructure/services/thumbnail_service.rs | 12 ++++++------ 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/common/di.rs b/src/common/di.rs index 97f1c790..2e4c8db8 100644 --- a/src/common/di.rs +++ b/src/common/di.rs @@ -363,9 +363,9 @@ impl AppServiceFactory { if self.config.features.enable_video_thumbnails && FfmpegVideoFrameService::is_available(&ffmpeg_path) { - let cpus = std::thread::available_parallelism() - .map(|n| n.get()) - .unwrap_or(4); + // effective_parallelism respects the CFS quota (--cpus), not + // just affinity — so ffmpeg fan-out matches the real core budget. + let cpus = crate::common::runtime::effective_parallelism(); let concurrency = std::env::var("OXICLOUD_VIDEO_THUMBNAIL_CONCURRENCY") .ok() .and_then(|v| v.parse::().ok()) diff --git a/src/infrastructure/services/image_transcode_service.rs b/src/infrastructure/services/image_transcode_service.rs index 4501f746..6b0b8bbe 100644 --- a/src/infrastructure/services/image_transcode_service.rs +++ b/src/infrastructure/services/image_transcode_service.rs @@ -31,13 +31,14 @@ pub const MAX_TRANSCODE_SIZE: u64 = 5 * 1024 * 1024; /// Minimum number of threads in the dedicated transcoding pool const MIN_TRANSCODE_THREADS: usize = 2; -/// Compute the number of transcoding threads: half the available CPUs, -/// with a floor of `MIN_TRANSCODE_THREADS`. `available_parallelism()` -/// respects cgroup limits (Docker/K8s) and CPU affinity masks. +/// Compute the number of transcoding threads: half the available CPUs, with a +/// floor of `MIN_TRANSCODE_THREADS`. Sized by +/// [`effective_parallelism`](crate::common::runtime::effective_parallelism), +/// which respects CPU affinity **and** the CFS quota (Docker/K8s `--cpus`) — +/// unlike bare `available_parallelism()`, which ignores the quota and would +/// over-size this CPU-bound pool under a container limit. fn transcode_thread_count() -> usize { - let cpus = std::thread::available_parallelism() - .map(|n| n.get()) - .unwrap_or(MIN_TRANSCODE_THREADS); + let cpus = crate::common::runtime::effective_parallelism(); (cpus / 2).max(MIN_TRANSCODE_THREADS) } diff --git a/src/infrastructure/services/thumbnail_service.rs b/src/infrastructure/services/thumbnail_service.rs index cbcd2b2a..8d8e37cf 100644 --- a/src/infrastructure/services/thumbnail_service.rs +++ b/src/infrastructure/services/thumbnail_service.rs @@ -107,8 +107,11 @@ const STREAM_TO_TEMP_TIMEOUT: Duration = Duration::from_secs(120); /// concurrency was halved to keep peak RAM in check. Decodes are now DCT-shrunk /// to the thumbnail size (~18–25 MB regardless of source resolution), so the RAM /// ceiling no longer forces throttling and we can saturate every core. Override -/// with `OXICLOUD_THUMBNAIL_DECODE_CONCURRENCY`. `available_parallelism()` -/// respects cgroup limits (Docker/K8s) and CPU affinity masks. +/// with `OXICLOUD_THUMBNAIL_DECODE_CONCURRENCY`. Sized by +/// [`effective_parallelism`](crate::common::runtime::effective_parallelism), +/// which respects CPU affinity **and** the CFS quota (`--cpus`) — so under a +/// container quota we don't over-permit concurrent CPU-heavy decodes onto cores +/// the scheduler can't give us. fn max_concurrent_decodes() -> usize { if let Some(n) = std::env::var(DECODE_CONCURRENCY_ENV) .ok() @@ -117,10 +120,7 @@ fn max_concurrent_decodes() -> usize { { return n; } - let cpus = std::thread::available_parallelism() - .map(|n| n.get()) - .unwrap_or(4); - cpus.max(2) + crate::common::runtime::effective_parallelism().max(2) } /// Thumbnail service for generating and caching image thumbnails