perf: replace SHA-256 with BLAKE3 in WebDAV/WOPI, fix JOIN index usage, add LIMIT to favorites, remove dead lru crate, trim tokio features

- 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)
This commit is contained in:
Dionisio
2026-03-02 02:13:08 +01:00
parent b06206207f
commit dd27872a8c
7 changed files with 16 additions and 51 deletions
@@ -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)
@@ -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
+2 -2
View File
@@ -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<Vec<String>, 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)>(
@@ -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<Response<Body>, 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
+4 -5
View File
@@ -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<String>,
@@ -165,7 +165,6 @@ async fn put_file(
req: Request<Body>,
) -> 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