perf(quota): stop recomputing storage usage on every GET /api/auth/me
GET /api/auth/me ran a synchronous O(N) SUM(size) over all the user's files plus an unconditional UPDATE of auth.users on every call — one of the most frequently hit endpoints — adding per-request latency, DB write load, dead tuples and WAL even when nothing changed. - /api/auth/me now serves the cached storage_used_bytes column instead of recomputing it inline. - New StorageUsageService::start_reconciliation_job runs a periodic sweep on the maintenance pool that keeps the cached value current for every mutation (uploads, deletes, trash), so freshness no longer depends on hitting /me. Interval via OXICLOUD_STORAGE_USAGE_RECONCILE_SECS (default 600s, floored at 30s; first sweep deferred one interval to avoid boot load). - update_storage_usage only writes when the value actually changes (IS DISTINCT FROM), so the sweep produces no dead tuple / WAL on no-ops. - New covering partial index idx_files_user_size_active makes the usage SUM an index-only scan instead of a heap scan over all the user's files. Also collapse the same pre-existing clippy collapsible_else_if in carddav_handler that blocks the -D warnings gate on this base. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -434,7 +434,7 @@ pub async fn refresh_token(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Return the authenticated user's profile, including live storage usage.
|
||||
/// Return the authenticated user's profile, including cached storage usage.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/auth/me",
|
||||
@@ -454,29 +454,13 @@ pub async fn get_current_user(
|
||||
.as_ref()
|
||||
.ok_or_else(|| AppError::internal_error("Authentication service not configured"))?;
|
||||
|
||||
// First, update the storage usage statistics
|
||||
// IMPORTANT: We await the calculation to return updated data
|
||||
if let Some(storage_usage_service) = state.storage_usage_service.as_ref() {
|
||||
// Calculate storage synchronously (we await the result)
|
||||
match storage_usage_service
|
||||
.update_user_storage_usage(user_id)
|
||||
.await
|
||||
{
|
||||
Ok(usage) => {
|
||||
tracing::info!(
|
||||
"Updated storage usage for user {}: {} bytes",
|
||||
user_id,
|
||||
usage
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Only log a warning, don't fail the entire request
|
||||
tracing::warn!("Failed to update storage usage for user {}: {}", user_id, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now get the user data WITH the updated storage
|
||||
// Storage usage is served from the cached `storage_used_bytes` column —
|
||||
// it is NOT recomputed here. Recomputing on this hot endpoint meant an
|
||||
// O(N) `SUM(size)` over all the user's files plus an `UPDATE` of
|
||||
// `auth.users` on every single call (one of the most frequent endpoints).
|
||||
// The cached value is kept current by the per-upload update and a periodic
|
||||
// background reconciliation sweep
|
||||
// (see `StorageUsageService::start_reconciliation_job`).
|
||||
let user = auth_service
|
||||
.auth_application_service
|
||||
.get_user_by_id(user_id)
|
||||
|
||||
@@ -141,12 +141,10 @@ fn strip_username_prefix(path: &str) -> &str {
|
||||
} else {
|
||||
&path[pos + 1..]
|
||||
}
|
||||
} else if uuid::Uuid::parse_str(path).is_ok() {
|
||||
path
|
||||
} else {
|
||||
if uuid::Uuid::parse_str(path).is_ok() {
|
||||
path
|
||||
} else {
|
||||
""
|
||||
}
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user