From 093400ce72e15b3cee74e126bc8c6f47d3653a52 Mon Sep 17 00:00:00 2001 From: Dionisio Date: Wed, 25 Feb 2026 10:02:25 +0100 Subject: [PATCH] =?UTF-8?q?perf:=20optimize=20empty=5Ftrash=20=E2=80=94=20?= =?UTF-8?q?remove=20redundant=20N+1=20loop,=20rely=20on=20bulk=20clear=5Ft?= =?UTF-8?q?rash()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing clear_trash() already performs bulk SQL DELETEs: 1. DELETE FROM storage.files WHERE user_id = $1 AND is_trashed = TRUE 2. DELETE FROM storage.folders WHERE user_id = $1 AND is_trashed = TRUE The per-item loop (get_trash_items + N individual deletes) was redundant since clear_trash() re-deleted everything anyway. Removed the loop entirely. Folder CASCADE (FK ON DELETE CASCADE) handles child folders and their files. PG trigger trg_files_decrement_blob_ref automatically decrements blob ref_counts for every deleted file row — no Rust-side remove_reference() call needed. Orphan blobs are cleaned by garbage collection. Result: O(2) SQL queries instead of O(N+2) for any trash size. --- src/application/services/trash_service.rs | 40 +++++++---------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/application/services/trash_service.rs b/src/application/services/trash_service.rs index d0f7c851..581b2707 100644 --- a/src/application/services/trash_service.rs +++ b/src/application/services/trash_service.rs @@ -672,37 +672,19 @@ impl TrashUseCase for TrashService { let user_uuid = Uuid::parse_str(user_id) .map_err(|e| DomainError::validation_error(format!("Invalid user ID: {}", e)))?; - // Get all items in the user's trash - let items = self.trash_repository.get_trash_items(&user_uuid).await?; - - // Permanently delete each item - for item in items { - match item.item_type() { - TrashedItemType::File => { - // Permanently delete the file - let file_id = item.original_id().to_string(); - if let Err(e) = self.file_write_port.delete_file_permanently(&file_id).await { - error!("Error permanently deleting file {}: {}", file_id, e); - } - } - TrashedItemType::Folder => { - // Permanently delete the folder - let folder_id = item.original_id().to_string(); - if let Err(e) = self - .folder_storage_port - .delete_folder_permanently(&folder_id) - .await - { - error!("Error permanently deleting folder {}: {}", folder_id, e); - } - } - } - } - - // Clear all trash records for this user + // clear_trash() already performs bulk SQL DELETEs in 2 queries: + // 1. DELETE FROM storage.files WHERE user_id = $1 AND is_trashed = TRUE + // 2. DELETE FROM storage.folders WHERE user_id = $1 AND is_trashed = TRUE + // + // Folder deletion cascades (FK ON DELETE CASCADE) to child folders and + // their files. The PG trigger `trg_files_decrement_blob_ref` automatically + // decrements blob ref_counts for every deleted file row — no Rust-side + // remove_reference() call is needed. + // + // Finally it clears the trash_items index for the user. self.trash_repository.clear_trash(&user_uuid).await?; - info!("Trash completely emptied for user {}", user_id); + info!("Trash emptied for user {}", user_id); Ok(()) } }