Files
Oxicloud/benches/ROUND8.md
T
Claude 79b94126be perf(authz): round 8 — cache the File/Folder grant-cascade decision for shared-album thumbnails
get_thumbnail_impl runs require_permission(Read) on every request. For a drive
member that's a drive_role_cache hit, but a shared-album recipient — granted a
folder (the album), not drive membership — fails the drive-role precheck and
falls through to file_cascade_grant_exists (a role_grants ⋈ folders lpath
ancestor query), once per file. Browsers revalidate immutable thumbnails
constantly, so the same (recipient, file, Read) decision was recomputed on
every thumbnail of every view — ~100 grant queries per 100-photo album per
navigate-away-and-back.

New cascade_grant_cache ((Subject, Resource, Permission) → bool, 30 s TTL)
memoises that decision. The check is NEVER skipped — the ordering is unchanged,
authz still runs on every request; only the result is cached, and only after
the drive-role precheck fails (so a later drive grant can't be shadowed by a
stale entry). Invalidation mirrors drive_role_cache's convention: explicit
invalidate_all on every File/Folder set_role/clear_role (immediate revoke on
the direct share path), 30 s TTL for the indirect paths (group membership,
moves, expiry) "rather than a deep invalidation tree".

Bench (bench_thumbnail_cascade_cache) with hard safety gates — recipient
allowed, outsider denied, and a clear_role revoke denies the very next check
(proving the grant-write flush): 100-photo album revalidation 2576 → 2.70
µs/thumb (~950x), 257.6 → 0.27 ms/view. Validated against the full
--cfg integration_tests authz suite (554 tests) + 524 workspace tests, clippy
-D warnings clean.

Deliberately not done: moving authz after the 304/cache short-circuit (a
security-posture change — a revoked user could serve cached thumbnails). With
the decision cached, the authz on the 304 path is now a memory hit, so the
"zero DB work on a 304" intent is restored without weakening the check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017aJu9ghvuT8WqC31ZEGTBA
2026-07-18 14:03:43 +00:00

4.5 KiB

Round 8 — shared-album thumbnail authz: cache the folder-grant cascade decision

Benchmark-gated, same rule as ROUND2-7: every change ships with a BEFORE/AFTER benchmark and equivalence/safety gates; an AFTER that doesn't beat its BEFORE gets rolled back. This round touches the authorization engine, so the bench carries hard safety gates (recipient allowed, outsider denied, and a revoke-denies-immediately test) and the change is additionally validated against the full --cfg integration_tests authz suite.

Measured on 4 cores / 15 GiB, local PostgreSQL 16 (fsync off), release profile.

Summary

# change key metric before → after
1 cascade_grant_cache for File/Folder Read checks shared-album thumbnail revalidation (100-photo) 2576 → 2.70 µs/thumb (~950x); 257.6 → 0.27 ms/view

[1] Shared-album thumbnails — folder-grant cascade query per thumbnail → cached

get_thumbnail_impl runs require_permission(Read, file) on every request, ahead of the ETag-304 and moka/disk cache short-circuits. For the owner (or any drive member) that's a drive_role_cache hit — ~1 µs, no query. But a shared-album recipient — someone granted a folder (the album), not drive membership — fails the drive-role precheck in PgAclEngine::check_inner and falls through to file_cascade_grant_exists: an role_grants ⋈ folders ltree-ancestor (lpath @>) query, once per file. Browsers revalidate immutable thumbnails constantly (If-None-Match), so the same (recipient, file, Read) decision was recomputed on every thumbnail of every view — a shared 100-photo album cost ~100 grant queries per "navigate away and back".

The safe fix keeps the check exactly where it is — authz is never skipped, the ordering is unchanged — and memoises only its result in a new cascade_grant_cache ((Subject, Resource, Permission) → bool, 30 s TTL). It's consulted only after the drive-role precheck fails, so a caller who later gains a drive grant short-circuits above it and can't be shadowed by a stale entry.

Invalidation mirrors drive_role_cache's documented convention exactly: explicit invalidate_all on every File/Folder set_role / clear_role (the direct share/revoke path — infrequent next to thumbnail reads, so a full flush is cheap and keeps a revoke immediate); the indirect paths (group-membership changes, resource moves, grant expires_at expiry) are caught by the 30 s TTL, "rather than a deep invalidation tree".

Safety gates in the bench (hard asserts): the folder-grant recipient is allowed on every album file, an outsider is denied, and — critically — after a warm cache serves allowed, a clear_role on the shared folder makes the very next check deny (proving the grant-write flush; without it the stale true would still serve). Also validated against the full --cfg integration_tests authz suite (grants, nested groups, drive membership, read-only freeze).

cargo run --release --features bench --example bench_thumbnail_cascade_cache
# thumbs=100 (recipient holds a folder grant, no drive membership)
# arm                          wall ms    µs/thumb
# BEFORE (query/thumb)          257.60     2576.04   <- folder-cascade query per thumbnail
# AFTER cold (first view)        84.18      841.76   <- distinct files miss+populate the cache
# AFTER warm (revalidation)       0.27        2.70   <- all cache hits (~950x vs BEFORE)
# Safety gates PASSED: recipient allowed, outsider denied, clear_role revoke
# denies immediately (grant write flushed the cache).

Notes

  • The batched search Read path (check_files_read_batch) is unchanged — it already resolves a page of files in one round-trip and isn't the per-thumbnail hot path; it neither reads nor writes this cache, so no consistency coupling is introduced.
  • First-view cost is unchanged (distinct files are cache misses that populate the cache); the win is on revalidation + repeat views, which is where the thumbnail traffic concentrates. A folder-level cascade cache would also cut the first-view N-queries to one-per-folder, but needs a file→parent-folder resolution and a wider invalidation story — deferred.
  • The ACL-before-304 ordering (running authz before the 304/cache short-circuits) is left intact — with the cascade decision now cached, the authz on the revalidation path is a memory hit, so the "zero DB work on a 304" intent is restored without moving (and thus without weakening) the security check.