perf(transcode): eliminate double buffer copy by accepting Bytes instead of &[u8]

Change ImageTranscodePort::get_transcoded signature from &[u8] to Bytes.
- Rayon closure now receives Bytes::clone() (O(1) ref-count) instead of .to_vec() (~5 MB copy)
- Fallback path returns owned Bytes directly instead of Bytes::from(to_vec()) (~5 MB copy)
- Caller passes content.clone() (O(1)) instead of implicit deref

Saves ~10 MB of allocation per transcode call on a 5 MB image.
This commit is contained in:
Dionisio
2026-02-24 12:48:08 +01:00
parent d907a3eb63
commit 0986ebf81c
3 changed files with 10 additions and 7 deletions
+1 -1
View File
@@ -90,7 +90,7 @@ pub trait ImageTranscodePort: Send + Sync + 'static {
async fn get_transcoded( async fn get_transcoded(
&self, &self,
file_id: &str, file_id: &str,
original_content: &[u8], original_content: Bytes,
original_mime: &str, original_mime: &str,
target_format: OutputFormat, target_format: OutputFormat,
) -> Result<(Bytes, String, bool), DomainError>; ) -> Result<(Bytes, String, bool), DomainError>;
@@ -88,7 +88,7 @@ impl FileRetrievalService {
return None; return None;
} }
let format = OutputFormat::WebP; let format = OutputFormat::WebP;
match transcode.get_transcoded(id, content, mime, format).await { match transcode.get_transcoded(id, content.clone(), mime, format).await {
Ok((transcoded, webp_mime, true)) => { Ok((transcoded, webp_mime, true)) => {
debug!( debug!(
"🖼️ WebP transcode: {} -> {} bytes ({:.0}% smaller)", "🖼️ WebP transcode: {} -> {} bytes ({:.0}% smaller)",
@@ -192,10 +192,13 @@ impl ImageTranscodeService {
/// Get transcoded version of an image. /// Get transcoded version of an image.
/// Returns `(content, mime_type, was_transcoded)`. /// Returns `(content, mime_type, was_transcoded)`.
///
/// Accepts `Bytes` (ref-counted) so callers avoid copying the buffer.
/// Cloning `Bytes` is O(1) — only an atomic increment.
pub async fn get_transcoded( pub async fn get_transcoded(
&self, &self,
file_id: &str, file_id: &str,
original_content: &[u8], original_content: Bytes,
original_mime: &str, original_mime: &str,
target_format: OutputFormat, target_format: OutputFormat,
) -> Result<(Bytes, String, bool), String> { ) -> Result<(Bytes, String, bool), String> {
@@ -228,13 +231,13 @@ impl ImageTranscodeService {
} }
// ── 3. Transcode on dedicated rayon pool (never blocks Tokio) ── // ── 3. Transcode on dedicated rayon pool (never blocks Tokio) ──
let content_owned = original_content.to_vec(); let content_for_rayon = original_content.clone(); // O(1) ref-count bump
let mime_owned = original_mime.to_string(); let mime_owned = original_mime.to_string();
let (tx, rx) = tokio::sync::oneshot::channel(); let (tx, rx) = tokio::sync::oneshot::channel();
transcode_pool().spawn(move || { transcode_pool().spawn(move || {
let result = transcode_image_blocking(&content_owned, &mime_owned, target_format); let result = transcode_image_blocking(&content_for_rayon, &mime_owned, target_format);
let _ = tx.send(result); let _ = tx.send(result);
}); });
@@ -256,7 +259,7 @@ impl ImageTranscodeService {
transcoded_size transcoded_size
); );
return Ok(( return Ok((
Bytes::from(original_content.to_vec()), original_content,
original_mime.to_string(), original_mime.to_string(),
false, false,
)); ));
@@ -394,7 +397,7 @@ impl ImageTranscodePort for ImageTranscodeService {
async fn get_transcoded( async fn get_transcoded(
&self, &self,
file_id: &str, file_id: &str,
original_content: &[u8], original_content: Bytes,
original_mime: &str, original_mime: &str,
target_format: PortOutputFormat, target_format: PortOutputFormat,
) -> Result<(Bytes, String, bool), DomainError> { ) -> Result<(Bytes, String, bool), DomainError> {