perf: round 11 — StoragePath joined-only, classifier fusion, memoized bodies, query-shape pack, SPA fine-grained stars
Backend (each change benchmark-gated with BEFORE replicas + equivalence gates; see examples/bench_round11_micro.rs, bench_round11_queries.rs, bench_log_writer.rs and benches/ROUND11.md — final numbers land in the follow-up doc commit): - StoragePath re-representation: single canonical joined String, segments derived on demand; File/Folder drop the duplicated path_string field (4000→1000 allocs per 500-row listing page) - Display classifier fusion: classify_display shares one stack-lowered extension across the three decision trees; call sites in FileDto, folder/favorites/recent handlers, trash, path-resolver (+ interning where Arc::from was still used) - /status.php and /openapi.json memoized into OnceLock<Bytes> (openapi rebuilt a 171 KiB spec per request: 2.8 ms → 18 ns) - NC upload-session PROPFIND: write! + pre-sized body + stack RFC2822 dates (2.3-2.6x, 2582→772 allocs at 256 chunks) - REST download: dead FileDto clone removed (capture mime/size + move) - CalendarEventDto/TrashedItem into_parts moves (11 KiB ical_data memcpy gone per CalDAV row); CardDAV getlastmodified stack render - 4xx path: borrowed ErrorResponse serialize, ErrorKind::as_str, not_found/already_exists clone kill - vCard emit via write!; search page moved out with into_iter skip/take; content-hit UUIDs parsed once; group last-user check via HashSet - RateLimiter: lock-free get + insert (and_upsert_with variant REJECTED by benchmark); CSRF token borrow-compare + borrowed cookie extraction - Thumbnail/preview ETags built from as_str (Debug-identical bytes) - Encrypted backend: encrypt_in_place_detached single-buffer write path, chunk-sized reserve in collect_stream; retry labels made lazy - PG: deferred upload registration 3→1 round-trips (persist_file CTE template); direct_grant_cache for Calendar/AddressBook/Playlist authz (single-flight + set_role/clear_role invalidation); expand_user tokio::join!; geo clusters min(uuid)::text; recluster face assignment batched into one UNNEST update - People recluster cosine: norms precomputed once (bit-identical gate) - NC capabilities poll logs demoted to debug; tracing-appender dep added for the log-writer benchmark Frontend: - ResourceList.selectedEntries O(N)-per-toggle → id-index projection O(k log k); favorites/recent consume the batchToolbar snippet param and drop their duplicate filter + dead selectedIds mirror - Recent: star state via new favoriteIds prop — a star click no longer rebuilds all N entries - admin timeAgo >30d fallback uses the cached Intl.DateTimeFormat - vitest gates in src/lib/components/round11.bench.test.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ABhTEHuGujvwoodh67Kga7
This commit is contained in:
@@ -117,6 +117,16 @@ const CASCADE_GRANT_CACHE_CAPACITY: u64 = 100_000;
|
||||
/// invalidation tree". Short enough that any such change takes effect in <1 min.
|
||||
const CASCADE_GRANT_CACHE_TTL: Duration = Duration::from_secs(30);
|
||||
|
||||
/// `direct_grant_cache` bound/TTL: memoises the Calendar / AddressBook /
|
||||
/// Playlist `role_grants` point decision — the only `check()` arms that had
|
||||
/// NO result cache, re-run on every CalDAV/CardDAV/music request by clients
|
||||
/// that poll continuously. Same invalidation contract as
|
||||
/// `cascade_grant_cache`: grant writes on these resource types flush the
|
||||
/// whole cache; group/expiry churn self-heals within the TTL
|
||||
/// (benches/ROUND11.md §Q2).
|
||||
const DIRECT_GRANT_CACHE_CAPACITY: u64 = 100_000;
|
||||
const DIRECT_GRANT_CACHE_TTL: Duration = Duration::from_secs(30);
|
||||
|
||||
/// `file_parent_cache` bound/TTL: `file_id → Option<folder_id>` point rows
|
||||
/// (~50 B each) resolved on the file-cascade path so an N-file album pays
|
||||
/// ONE folder-cascade query instead of N (ROUND9). Parentage changes only
|
||||
@@ -212,6 +222,11 @@ pub struct PgAclEngine {
|
||||
/// only positively-or-negatively for at most the TTL. A revoke via
|
||||
/// `clear_role` flushes immediately; anything missed self-heals in ≤30 s.
|
||||
cascade_grant_cache: Cache<(Subject, Resource, Permission), bool>,
|
||||
|
||||
/// Memoised Calendar/AddressBook/Playlist direct-grant decision (the
|
||||
/// top-level resources with no cascade parent). See
|
||||
/// `DIRECT_GRANT_CACHE_CAPACITY` for the contract.
|
||||
direct_grant_cache: Cache<(Subject, Resource, Permission), bool>,
|
||||
/// `file_id → Option<parent folder_id>` memo for the file-cascade
|
||||
/// decomposition (see `cascade_grant_cached`): resolving the parent lets
|
||||
/// a whole folder's files share ONE folder-cascade decision, so a shared
|
||||
@@ -315,6 +330,10 @@ impl PgAclEngine {
|
||||
.max_capacity(CASCADE_GRANT_CACHE_CAPACITY)
|
||||
.time_to_live(CASCADE_GRANT_CACHE_TTL)
|
||||
.build(),
|
||||
direct_grant_cache: Cache::builder()
|
||||
.max_capacity(DIRECT_GRANT_CACHE_CAPACITY)
|
||||
.time_to_live(DIRECT_GRANT_CACHE_TTL)
|
||||
.build(),
|
||||
file_parent_cache: Cache::builder()
|
||||
.max_capacity(FILE_PARENT_CACHE_CAPACITY)
|
||||
.time_to_live(FILE_PARENT_CACHE_TTL)
|
||||
@@ -395,6 +414,10 @@ impl PgAclEngine {
|
||||
.max_capacity(1)
|
||||
.time_to_live(Duration::from_secs(1))
|
||||
.build(),
|
||||
direct_grant_cache: Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(Duration::from_secs(1))
|
||||
.build(),
|
||||
file_parent_cache: Cache::builder()
|
||||
.max_capacity(1)
|
||||
.time_to_live(Duration::from_secs(1))
|
||||
@@ -568,26 +591,38 @@ impl PgAclEngine {
|
||||
// belong to the Internal virtual group. Unknown user (no row) is
|
||||
// treated as external to fail closed: a deleted or bogus user_id
|
||||
// must not gain implicit Internal membership.
|
||||
//
|
||||
// The `is_external` point read and the recursive groups CTE are
|
||||
// independent — `join!` overlaps their round-trips on every cold
|
||||
// expansion instead of paying them serially (benches/ROUND11.md
|
||||
// §Q3; the ROUND9/10 pattern).
|
||||
counters.sql_queries.fetch_add(1, Ordering::Relaxed);
|
||||
let is_external: bool =
|
||||
sqlx::query_scalar("SELECT is_external FROM auth.users WHERE id = $1")
|
||||
let is_external_fut = async {
|
||||
sqlx::query_scalar::<_, bool>("SELECT is_external FROM auth.users WHERE id = $1")
|
||||
.bind(user_id)
|
||||
.fetch_optional(self.pool.as_ref())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DomainError::internal_error("PgAcl", format!("lookup is_external: {e}"))
|
||||
})?
|
||||
.unwrap_or(true);
|
||||
|
||||
if !is_external {
|
||||
})
|
||||
.map(|row| row.unwrap_or(true))
|
||||
};
|
||||
let groups_fut = async {
|
||||
match &self.group_repo {
|
||||
Some(repo) => {
|
||||
counters.sql_queries.fetch_add(1, Ordering::Relaxed);
|
||||
repo.groups_for_user(user_id).await.map(Some).map_err(|e| {
|
||||
DomainError::internal_error("PgAcl", format!("groups_for_user: {e}"))
|
||||
})
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
};
|
||||
let (is_external, direct) = tokio::join!(is_external_fut, groups_fut);
|
||||
if !is_external? {
|
||||
set.insert(INTERNAL_GROUP_ID);
|
||||
}
|
||||
|
||||
if let Some(repo) = &self.group_repo {
|
||||
counters.sql_queries.fetch_add(1, Ordering::Relaxed);
|
||||
let direct = repo.groups_for_user(user_id).await.map_err(|e| {
|
||||
DomainError::internal_error("PgAcl", format!("groups_for_user: {e}"))
|
||||
})?;
|
||||
if let Some(direct) = direct? {
|
||||
set.extend(direct);
|
||||
}
|
||||
|
||||
@@ -1099,6 +1134,48 @@ impl PgAclEngine {
|
||||
/// (on File/Folder grant writes — it holds file AND folder decisions in
|
||||
/// the same map) and the 30 s TTL (indirect changes, incl. moves for the
|
||||
/// parent memo) keep it fresh. See the `cascade_grant_cache` field doc.
|
||||
/// Cached wrapper for the Calendar/AddressBook/Playlist direct-grant
|
||||
/// decision (`try_get_with`: a cold herd on one key coalesces into ONE
|
||||
/// loader run, the ROUND10 single-flight pattern; loader errors are
|
||||
/// never cached). `resource_type` is the `role_grants.resource_type`
|
||||
/// discriminant for `resource`.
|
||||
async fn direct_grant_cached(
|
||||
&self,
|
||||
subject: Subject,
|
||||
resource: Resource,
|
||||
permission: Permission,
|
||||
resource_type: &'static str,
|
||||
id: Uuid,
|
||||
counters: &QueryCounters,
|
||||
) -> Result<bool, DomainError> {
|
||||
if let Some(allowed) = self
|
||||
.direct_grant_cache
|
||||
.get(&(subject, resource, permission))
|
||||
.await
|
||||
{
|
||||
counters.cache_hit.fetch_add(1, Ordering::Relaxed);
|
||||
return Ok(allowed);
|
||||
}
|
||||
self.direct_grant_cache
|
||||
.try_get_with((subject, resource, permission), async {
|
||||
let (subject_types, subject_ids) =
|
||||
self.subject_match_set(subject, counters).await?;
|
||||
self.direct_grant_exists(
|
||||
&subject_types,
|
||||
&subject_ids,
|
||||
permission,
|
||||
resource_type,
|
||||
id,
|
||||
counters,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
.map_err(|e: Arc<DomainError>| {
|
||||
DomainError::internal_error("PgAcl", format!("direct grant load: {e}"))
|
||||
})
|
||||
}
|
||||
|
||||
async fn cascade_grant_cached(
|
||||
&self,
|
||||
subject: Subject,
|
||||
@@ -1493,24 +1570,13 @@ impl PgAclEngine {
|
||||
// round-trip — no drive_role_cache short-circuit (no
|
||||
// drive), no cascade.
|
||||
Resource::Calendar(id) => {
|
||||
let (subject_types, subject_ids) =
|
||||
self.subject_match_set(subject, counters).await?;
|
||||
self.direct_grant_exists(
|
||||
&subject_types,
|
||||
&subject_ids,
|
||||
permission,
|
||||
"calendar",
|
||||
id,
|
||||
counters,
|
||||
)
|
||||
.await
|
||||
self.direct_grant_cached(subject, resource, permission, "calendar", id, counters)
|
||||
.await
|
||||
}
|
||||
Resource::AddressBook(id) => {
|
||||
let (subject_types, subject_ids) =
|
||||
self.subject_match_set(subject, counters).await?;
|
||||
self.direct_grant_exists(
|
||||
&subject_types,
|
||||
&subject_ids,
|
||||
self.direct_grant_cached(
|
||||
subject,
|
||||
resource,
|
||||
permission,
|
||||
"address_book",
|
||||
id,
|
||||
@@ -1519,17 +1585,8 @@ impl PgAclEngine {
|
||||
.await
|
||||
}
|
||||
Resource::Playlist(id) => {
|
||||
let (subject_types, subject_ids) =
|
||||
self.subject_match_set(subject, counters).await?;
|
||||
self.direct_grant_exists(
|
||||
&subject_types,
|
||||
&subject_ids,
|
||||
permission,
|
||||
"playlist",
|
||||
id,
|
||||
counters,
|
||||
)
|
||||
.await
|
||||
self.direct_grant_cached(subject, resource, permission, "playlist", id, counters)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2874,6 +2931,13 @@ impl AuthorizationEngine for PgAclEngine {
|
||||
if matches!(resource, Resource::File(_) | Resource::Folder(_)) {
|
||||
self.invalidate_cascade_grant_cache_all().await;
|
||||
}
|
||||
// Same immediacy contract for the memoised top-level decisions.
|
||||
if matches!(
|
||||
resource,
|
||||
Resource::Calendar(_) | Resource::AddressBook(_) | Resource::Playlist(_)
|
||||
) {
|
||||
self.direct_grant_cache.invalidate_all();
|
||||
}
|
||||
|
||||
Self::row_to_grant(row)
|
||||
}
|
||||
@@ -2903,6 +2967,14 @@ impl AuthorizationEngine for PgAclEngine {
|
||||
if matches!(resource, Resource::File(_) | Resource::Folder(_)) {
|
||||
self.invalidate_cascade_grant_cache_all().await;
|
||||
}
|
||||
// A revoked calendar/address-book/playlist grant must fail the next
|
||||
// check now, not in ≤30 s (see `set_role`).
|
||||
if matches!(
|
||||
resource,
|
||||
Resource::Calendar(_) | Resource::AddressBook(_) | Resource::Playlist(_)
|
||||
) {
|
||||
self.direct_grant_cache.invalidate_all();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user