From b5652b029dcef320bcae63fc9f0ef2d80e6d6212 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Mon, 23 Feb 2026 22:49:34 +0100 Subject: [PATCH] perf: offload MD5 checksum to spawn_blocking in chunked uploads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit md5::compute(&data) is CPU-bound (~1.2ms per 5MB chunk) and was blocking the Tokio worker thread. Move it to the blocking thread-pool via spawn_blocking so the async worker is freed in ~5µs. Bytes::clone is O(1) (Arc increment) so no extra copy overhead. --- .../services/chunked_upload_service.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/infrastructure/services/chunked_upload_service.rs b/src/infrastructure/services/chunked_upload_service.rs index f9a6b548..e4fa885f 100644 --- a/src/infrastructure/services/chunked_upload_service.rs +++ b/src/infrastructure/services/chunked_upload_service.rs @@ -499,10 +499,18 @@ impl ChunkedUploadService { )); } - // Verify checksum if provided + // Verify checksum if provided — MD5 is CPU-bound (~1.2 ms per 5 MB), + // so we offload it to the blocking thread-pool to keep the Tokio + // worker free for other connections. if let Some(ref expected_checksum) = checksum { - let actual_checksum = format!("{:x}", md5::compute(&data)); - if &actual_checksum != expected_checksum { + let data_clone = data.clone(); // Bytes::clone is O(1) — just an Arc increment + let actual_checksum = tokio::task::spawn_blocking(move || { + format!("{:x}", md5::compute(&data_clone)) + }) + .await + .map_err(|e| format!("MD5 checksum task failed: {e}"))?; + + if actual_checksum != *expected_checksum { return Err(format!( "Checksum mismatch: expected {}, got {}", expected_checksum, actual_checksum