From dd27872a8c3e508712e24aa52725d36f8babf3b2 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Mon, 2 Mar 2026 02:13:08 +0100 Subject: [PATCH] perf: replace SHA-256 with BLAKE3 in WebDAV/WOPI, fix JOIN index usage, add LIMIT to favorites, remove dead lru crate, trim tokio features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WebDAV PUT and WOPI PutFile: replace sha2::Sha256 with blake3::Hasher (~5x faster hashing, compatible with dedup service) - Fix TEXT↔UUID JOIN anti-pattern in favorites and recent_items repos (enables PK index usage) - Add LIMIT 500 to get_favorites query to prevent unbounded memory allocation - Remove unused lru crate from Cargo.toml (superseded by moka) - Replace tokio features=["full"] with explicit feature list (removes signal, process, test-util) --- Cargo.lock | 35 +------------------ Cargo.toml | 3 +- .../pg/favorites_pg_repository.rs | 5 +-- .../pg/recent_items_pg_repository.rs | 4 +-- src/infrastructure/services/dedup_service.rs | 4 +-- src/interfaces/api/handlers/webdav_handler.rs | 7 ++-- src/interfaces/api/handlers/wopi_handler.rs | 9 +++-- 7 files changed, 16 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b75a192e..8249d5ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -838,12 +838,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1074,7 +1068,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -1082,11 +1076,6 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] [[package]] name = "hashlink" @@ -1557,15 +1546,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "lru" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" -dependencies = [ - "hashbrown 0.16.1", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -1833,7 +1813,6 @@ dependencies = [ "image", "infer", "jsonwebtoken", - "lru", "md5", "mimalloc", "mime_guess", @@ -2590,16 +2569,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - [[package]] name = "signature" version = "2.2.0" @@ -3043,9 +3012,7 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index 93456abd..2f52bb44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ default-run = "oxicloud" [dependencies] mimalloc = { version = "0.1.48", default-features = false } axum = { version = "0.8.8", features = ["multipart", "http1", "tokio", "macros"] } -tokio = { version = "1.49.0", features = ["full"] } +tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "io-util", "net", "time", "sync", "fs"] } tokio-util = { version = "0.7.18", features = ["io", "codec", "compat"] } tokio-stream = { version = "0.1.18", features = ["fs"] } bytes = "1.11.1" @@ -37,7 +37,6 @@ rand_core = { version = "0.6", features = ["std", "getrandom"] } hyper = { version = "1.8.1", features = ["full"] } quick-xml = "0.39.0" dotenvy = "0.15.7" -lru = "0.16.3" moka = { version = "0.12", features = ["future", "sync"] } http-range-header = "0.4" image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] } diff --git a/src/infrastructure/repositories/pg/favorites_pg_repository.rs b/src/infrastructure/repositories/pg/favorites_pg_repository.rs index f844e596..039bf16e 100644 --- a/src/infrastructure/repositories/pg/favorites_pg_repository.rs +++ b/src/infrastructure/repositories/pg/favorites_pg_repository.rs @@ -39,11 +39,12 @@ impl FavoritesRepositoryPort for FavoritesPgRepository { COALESCE(f.updated_at, fld.updated_at) AS "modified_at" FROM auth.user_favorites uf LEFT JOIN storage.files f ON uf.item_type = 'file' - AND uf.item_id = f.id::TEXT + AND f.id = uf.item_id::UUID LEFT JOIN storage.folders fld ON uf.item_type = 'folder' - AND uf.item_id = fld.id::TEXT + AND fld.id = uf.item_id::UUID WHERE uf.user_id = $1::TEXT ORDER BY uf.created_at DESC + LIMIT 500 "#, ) .bind(user_uuid) diff --git a/src/infrastructure/repositories/pg/recent_items_pg_repository.rs b/src/infrastructure/repositories/pg/recent_items_pg_repository.rs index c5249652..5e69365f 100644 --- a/src/infrastructure/repositories/pg/recent_items_pg_repository.rs +++ b/src/infrastructure/repositories/pg/recent_items_pg_repository.rs @@ -38,9 +38,9 @@ impl RecentItemsRepositoryPort for RecentItemsPgRepository { COALESCE(f.folder_id::TEXT, fld.parent_id::TEXT) AS "parent_id" FROM auth.user_recent_files ur LEFT JOIN storage.files f ON ur.item_type = 'file' - AND ur.item_id = f.id::TEXT + AND f.id = ur.item_id::UUID LEFT JOIN storage.folders fld ON ur.item_type = 'folder' - AND ur.item_id = fld.id::TEXT + AND fld.id = ur.item_id::UUID WHERE ur.user_id = $1::TEXT ORDER BY ur.accessed_at DESC LIMIT $2 diff --git a/src/infrastructure/services/dedup_service.rs b/src/infrastructure/services/dedup_service.rs index a5135b33..68894ea4 100644 --- a/src/infrastructure/services/dedup_service.rs +++ b/src/infrastructure/services/dedup_service.rs @@ -268,7 +268,7 @@ impl DedupService { /// never held during disk I/O. /// /// If `pre_computed_hash` is `Some`, the file will NOT be re-read for - /// SHA-256 — saving one full sequential read (the biggest I/O win). + /// BLAKE3 — saving one full sequential read (the biggest I/O win). pub async fn store_from_file( &self, source_path: &Path, @@ -615,7 +615,7 @@ impl DedupService { /// of `VERIFY_CONCURRENCY` using `buffer_unordered`. pub async fn verify_integrity(&self) -> Result, DomainError> { /// Max blobs verified concurrently. Each spawns a blocking - /// thread for SHA-256 so this also caps blocking-pool pressure. + /// thread for BLAKE3 so this also caps blocking-pool pressure. const VERIFY_CONCURRENCY: usize = 16; let mut row_stream = sqlx::query_as::<_, (String, i64)>( diff --git a/src/interfaces/api/handlers/webdav_handler.rs b/src/interfaces/api/handlers/webdav_handler.rs index 6298861a..ca7fa198 100644 --- a/src/interfaces/api/handlers/webdav_handler.rs +++ b/src/interfaces/api/handlers/webdav_handler.rs @@ -632,7 +632,7 @@ async fn handle_head( * Handles PUT requests to create or update files. * * **Streaming implementation**: the request body is spooled to a temp file - * with incremental SHA-256 hashing. Peak RAM usage is ~256 KB regardless + * with incremental BLAKE3 hashing. Peak RAM usage is ~256 KB regardless * of file size. The temp file is then atomically moved into blob storage * via `update_file_streaming`. * @@ -647,7 +647,6 @@ async fn handle_put( path: String, ) -> Result, AppError> { use http_body_util::BodyStream; - use sha2::{Digest, Sha256}; use tokio::io::AsyncWriteExt; use tokio_stream::StreamExt; @@ -679,7 +678,7 @@ async fn handle_put( .await .map_err(|e| AppError::internal_error(format!("Failed to open temp file: {}", e)))?; - let mut hasher = Sha256::new(); + let mut hasher = blake3::Hasher::new(); let mut total_bytes: usize = 0; let mut stream = BodyStream::new(req.into_body()); @@ -708,7 +707,7 @@ async fn handle_put( .map_err(|e| AppError::internal_error(format!("Failed to flush temp file: {}", e)))?; drop(file); - let hash = hex::encode(hasher.finalize()); + let hash = hasher.finalize().to_hex().to_string(); // ── Atomic store: temp file → dedup blob + DB metadata update ── let result = file_upload_service diff --git a/src/interfaces/api/handlers/wopi_handler.rs b/src/interfaces/api/handlers/wopi_handler.rs index 74957c2c..f160b9b3 100644 --- a/src/interfaces/api/handlers/wopi_handler.rs +++ b/src/interfaces/api/handlers/wopi_handler.rs @@ -155,7 +155,7 @@ async fn get_file( /// POST /wopi/files/{file_id}/contents — PutFile /// /// **Streaming implementation**: the request body is spooled to a temp file -/// with incremental SHA-256 hashing. Peak RAM usage is ~256 KB regardless +/// with incremental BLAKE3 hashing. Peak RAM usage is ~256 KB regardless /// of file size (previously buffered the entire body as `Bytes`). async fn put_file( Path(file_id): Path, @@ -165,7 +165,6 @@ async fn put_file( req: Request, ) -> Response { use http_body_util::BodyStream; - use sha2::{Digest, Sha256}; use tokio::io::AsyncWriteExt; use tokio_stream::StreamExt; @@ -218,7 +217,7 @@ async fn put_file( Err(_) => return StatusCode::NOT_FOUND.into_response(), }; - // ── Streaming spool: body → temp file + incremental SHA-256 ── + // ── Streaming spool: body → temp file + incremental BLAKE3 ── let temp_file = match tempfile::NamedTempFile::new() { Ok(f) => f, Err(e) => { @@ -237,7 +236,7 @@ async fn put_file( }; let content_type = file.mime_type.clone(); - let mut hasher = Sha256::new(); + let mut hasher = blake3::Hasher::new(); let mut total_bytes: u64 = 0; let mut stream = BodyStream::new(req.into_body()); @@ -267,7 +266,7 @@ async fn put_file( } drop(file_out); - let hash = hex::encode(hasher.finalize()); + let hash = hasher.finalize().to_hex().to_string(); // ── Atomic store: temp file → dedup blob + DB metadata update ── let result = state