perf: round 14 — faces narrow projection, auth per-request allocs, CalDAV emit buffers, frontend set churn
Benchmark-gated (benches/ROUND14.md); every change ships a BEFORE/AFTER
benchmark with an equivalence gate and is rolled back on regression (the
rule is encoded as a GATE FAIL exit / threshold expect).
Backend
- Q1 faces_for_file → narrow face_boxes_for_file(id, person_id, bbox) with the
caller filter pushed into SQL: drops the 2 KiB embedding BYTEA + 6 unused
columns per face. 15-face lightbox open 0.312→0.219 ms, 32 KB→840 B/req.
- A1 cookie auth uses the borrow-only extract_cookie_str (already backs CSRF)
instead of extract_cookie_value's owned String: -1 alloc/cookie request.
- A2 compute_relevance ASCII case-fold fast path vs name.to_lowercase() per
result row (Unicode fallback preserved): 1.40x, 12→3 allocs/page.
- A3 sub pre-parsed to Uuid at decode time (TokenClaims.sub_id) vs re-parsing
the 36-char claim on every request incl. cache hits: 22.7→0.7 ns.
- A4 auth + NextCloud middlewares borrow request.headers() instead of taking
axum's HeaderMap extractor (a full map clone): 2→0 allocs/authed request.
- A5 CalDAV getlastmodified via the stack rfc2822_utc (byte-identical to
chrono) vs a per-event to_rfc2822() heap String: 5→0 allocs.
- A6 CalDAV per-event href + quoted etag written into reused page buffers vs a
fresh format! pair per event: 3.48x, 240→6 allocs/40-event page.
Frontend
- F1 t() shares one frozen EMPTY_PARAMS for the no-interpolation call forms vs
a throwaway {} per call: -1 alloc/call.
- F2 favorites favoriteIds is a persistent SvelteSet with per-page add (clear
on reset) vs a brand-new set over the whole accumulated list each page:
22.3x over a 40-page drain (O(N^2)→O(N)).
Verified: cargo check --all-targets, cargo clippy -D warnings, both bench
packs (GATE PASS), frontend npm run check + vitest (4/4). ROUND14.md also
records the investigated-but-deferred backlog (music N+1, contact vcard
over-fetch, CachedBlobBackend syscalls, ResourceList.sections builder, etc.).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PymgCdK78NzUF3oRAQCJfN
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
extract::{FromRequestParts, Request, State},
|
||||
http::{HeaderMap, StatusCode, header, request::Parts},
|
||||
http::{StatusCode, header, request::Parts},
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
@@ -163,11 +163,17 @@ impl IntoResponse for AuthError {
|
||||
/// then the cookie fallback.
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AuthError> {
|
||||
let auth_header = headers
|
||||
// Borrow the Authorization header straight from the request instead of
|
||||
// taking axum's `HeaderMap` extractor, which clones the whole map (~2
|
||||
// allocs) on every authenticated request purely to read it
|
||||
// (benches/ROUND14.md §A4). The borrow is dead by the time each arm
|
||||
// reaches `request.extensions_mut()` / `next.run(request)` (NLL), so no
|
||||
// owned copy is needed.
|
||||
let auth_header = request
|
||||
.headers()
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok());
|
||||
|
||||
@@ -186,9 +192,14 @@ pub async fn auth_middleware(
|
||||
"Token validated successfully for user: {}",
|
||||
claims.username
|
||||
);
|
||||
let user_id = Uuid::parse_str(&claims.sub).map_err(|_| {
|
||||
AuthError::InvalidToken("Invalid user ID in token".to_string())
|
||||
})?;
|
||||
// Pre-parsed at decode time (benches/ROUND14.md §A3);
|
||||
// nil only for a malformed sub, which we reject as before.
|
||||
let user_id = claims.sub_id;
|
||||
if user_id.is_nil() {
|
||||
return Err(AuthError::InvalidToken(
|
||||
"Invalid user ID in token".to_string(),
|
||||
));
|
||||
}
|
||||
// A cryptographically valid token must not outlive the
|
||||
// account: re-check the live record so deactivation,
|
||||
// deletion and demotion take effect within the flags-cache
|
||||
@@ -296,19 +307,23 @@ pub async fn auth_middleware(
|
||||
use crate::interfaces::api::cookie_auth;
|
||||
|
||||
if let Some(token_str) =
|
||||
cookie_auth::extract_cookie_value(&headers, cookie_auth::ACCESS_COOKIE)
|
||||
cookie_auth::extract_cookie_str(request.headers(), cookie_auth::ACCESS_COOKIE)
|
||||
&& !token_str.is_empty()
|
||||
{
|
||||
tracing::debug!("Processing cookie-based authentication");
|
||||
|
||||
if let Some(auth_service) = state.auth_service.as_ref() {
|
||||
let token_service = &auth_service.token_service;
|
||||
match token_service.validate_token(&token_str) {
|
||||
match token_service.validate_token(token_str) {
|
||||
Ok(claims) => {
|
||||
tracing::debug!("Cookie token validated for user: {}", claims.username);
|
||||
let user_id = Uuid::parse_str(&claims.sub).map_err(|_| {
|
||||
AuthError::InvalidToken("Invalid user ID in token".to_string())
|
||||
})?;
|
||||
// Pre-parsed at decode time (benches/ROUND14.md §A3).
|
||||
let user_id = claims.sub_id;
|
||||
if user_id.is_nil() {
|
||||
return Err(AuthError::InvalidToken(
|
||||
"Invalid user ID in token".to_string(),
|
||||
));
|
||||
}
|
||||
// Same live-account re-check as the Bearer path. On
|
||||
// revocation we fall through (rather than erroring) so the
|
||||
// browser receives the standard 401 and redirects to
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use axum::{
|
||||
extract::{Request, State},
|
||||
http::{HeaderMap, StatusCode, header},
|
||||
http::{StatusCode, header},
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
@@ -70,13 +70,16 @@ impl IntoResponse for NextcloudAuthError {
|
||||
|
||||
pub async fn basic_auth_middleware(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, NextcloudAuthError> {
|
||||
tracing::debug!("[NC] {} {}", request.method(), request.uri());
|
||||
|
||||
let auth_header = headers
|
||||
// Borrow the Authorization header directly rather than cloning the whole
|
||||
// HeaderMap per NC sync request; the borrow ends at `parse_basic_auth`
|
||||
// below, before any request mutation (benches/ROUND14.md §A4).
|
||||
let auth_header = request
|
||||
.headers()
|
||||
.get(header::AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.ok_or_else(|| {
|
||||
|
||||
Reference in New Issue
Block a user