perf: eliminate Vec<u8> buffer paths — all uploads now stream to disk
Issue #4 (HIGH): save_file(Vec<u8>) and update_file_content(Vec<u8>) accepted up to 10 MB of contiguous memory per request. While the main upload paths already used streaming, the WebDAV compat methods (create_file, update_file) and the empty-file handler still used the buffered path, creating a .to_vec() copy. Changes: - FileWritePort trait: remove save_file(Vec<u8>) and update_file_content(Vec<u8>) — only streaming variants remain - FileUploadUseCase trait: remove upload_file(Vec<u8>) - file_upload_service.rs: create_file() and update_file() now spool &[u8] to NamedTempFile + Sha256::digest, then delegate to streaming path (save_file_from_temp / update_file_streaming) - file_handler.rs: empty file uploads use upload_file_streaming with - FileBlobWriteRepository: remove save_file and update_file_content impls - StubFileWritePort, StubFileUploadUseCase, MockFileRepository: remove corresponding dead method impls Impact: impossible to accidentally use a buffered upload path. All content goes through streaming with ~256 KB peak RAM. -166 LOC.
This commit is contained in:
@@ -15,13 +15,18 @@ use crate::common::errors::DomainError;
|
||||
|
||||
/// Primary port for file upload operations.
|
||||
///
|
||||
/// All upload paths converge on streaming-to-disk:
|
||||
/// **All upload paths converge on streaming-to-disk** — no method accepts
|
||||
/// `Vec<u8>` for content. Even `create_file` / `update_file` (WebDAV
|
||||
/// helpers that receive `&[u8]`) spool to a temp file internally so that
|
||||
/// peak RAM stays at ~256 KB regardless of file size.
|
||||
///
|
||||
/// - Normal uploads: handler spools multipart to temp file → `upload_file_streaming`
|
||||
/// - WebDAV PUT: small in-memory buffer → `upload_file`
|
||||
/// - Chunked uploads: chunks already on disk → `upload_file_from_path`
|
||||
/// - WebDAV PUT (new): handler streams to temp file → `update_file_streaming`
|
||||
/// - WebDAV PUT (small/compat): `create_file` / `update_file` spool internally
|
||||
#[async_trait]
|
||||
pub trait FileUploadUseCase: Send + Sync + 'static {
|
||||
/// Upload from a temp file already on disk (true streaming, ~64 KB RAM).
|
||||
/// Upload from a temp file already on disk (true streaming, ~256 KB RAM).
|
||||
///
|
||||
/// When `pre_computed_hash` is `Some`, the blob store skips the hash
|
||||
/// re-read — the handler already computed it during the multipart spool.
|
||||
@@ -35,19 +40,6 @@ pub trait FileUploadUseCase: Send + Sync + 'static {
|
||||
pre_computed_hash: Option<String>,
|
||||
) -> Result<FileDto, DomainError>;
|
||||
|
||||
/// Upload from in-memory bytes (for small payloads: WebDAV, empty files).
|
||||
///
|
||||
/// Only used for WebDAV PUT and empty files where the content is already
|
||||
/// buffered by the protocol handler. For normal uploads, prefer
|
||||
/// `upload_file_streaming`.
|
||||
async fn upload_file(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<FileDto, DomainError>;
|
||||
|
||||
/// Upload from a file already assembled on disk (chunked uploads).
|
||||
///
|
||||
/// Same as `upload_file_streaming` but with a separate name for clarity.
|
||||
|
||||
@@ -197,15 +197,6 @@ pub struct CopyFolderTreeResult {
|
||||
/// and deferred registration for the write-behind cache.
|
||||
#[async_trait]
|
||||
pub trait FileWritePort: Send + Sync + 'static {
|
||||
/// Saves a new file from bytes.
|
||||
async fn save_file(
|
||||
&self,
|
||||
name: String,
|
||||
folder_id: Option<String>,
|
||||
content_type: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<File, DomainError>;
|
||||
|
||||
/// Streaming upload — saves a file from a temp file already on disk.
|
||||
///
|
||||
/// When `pre_computed_hash` is provided, the dedup service skips the
|
||||
@@ -233,10 +224,6 @@ pub trait FileWritePort: Send + Sync + 'static {
|
||||
/// Deletes a file.
|
||||
async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
||||
|
||||
/// Updates the content of an existing file.
|
||||
async fn update_file_content(&self, file_id: &str, content: Vec<u8>)
|
||||
-> Result<(), DomainError>;
|
||||
|
||||
/// Streaming update — replaces file content from a temp file on disk.
|
||||
///
|
||||
/// When `pre_computed_hash` is provided, the dedup service skips the
|
||||
|
||||
Reference in New Issue
Block a user