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:
Claude
2026-07-18 22:02:00 +00:00
parent 637478e7bd
commit 221c1f31b0
54 changed files with 4060 additions and 630 deletions
@@ -0,0 +1,250 @@
import { describe, expect, it } from 'vitest';
/**
* Benchmark gates for the round-11 SPA items (see benches/ROUND11.md):
*
* [1] `ResourceList.selectedEntries` re-filtered the ENTIRE items array on
* every selection change once the batch toolbar was mounted — and the
* favorites/recent hosts ignored the snippet param and recomputed their
* own `entries.filter(...)` shadow, so each toggle ran TWO full O(N)
* scans (O(N²)-ish across a shift-range gesture). The shipped shape
* derives an id→index Map (rebuilt only when `items` changes) and
* projects the selection in O(k · log k), preserving item order; hosts
* now consume the snippet param.
*
* [2] The Recent page mapper baked `favoriteIds.has(id)` into every entry,
* subscribing the whole O(N) map to the SvelteSet — one star click
* rebuilt all N entries and re-rendered every visible row. The shipped
* shape reads membership in the star widget via ResourceList's new
* `favoriteIds` prop, so the mapper no longer depends on the set.
*
* [3] The admin "time ago" >30-day fallback called `toLocaleDateString()`
* (a fresh Intl.DateTimeFormat per call) instead of the cached
* `dateTimeFormatFor` the rest of the app uses.
*
* All modeled as pure replicas of the derive bodies with instrumentation
* counters (the listDerives.bench.test.ts convention).
*/
interface Entry {
id: string;
name: string;
}
const buildItems = (n: number): Entry[] =>
Array.from({ length: n }, (_, i) => ({ id: `it-${i}`, name: `Item ${i}` }));
/** BEFORE — component derive + host shadow, each a full O(N) scan. */
function selectedBefore(
items: Entry[],
selected: Set<string>,
counter: { comparisons: number }
): { component: Entry[]; host: Entry[] } {
const component = items.filter((i) => {
counter.comparisons++;
return selected.has(i.id);
});
const host = items.filter((i) => {
counter.comparisons++;
return selected.has(i.id);
});
return { component, host };
}
/** AFTER — id→index Map projection, index rebuilt only on items change. */
function makeAfterProjector(items: Entry[]) {
const indexById = new Map(items.map((i, idx) => [i.id, idx]));
return (selected: Set<string>, counter: { comparisons: number }): Entry[] => {
const picked: { idx: number; item: Entry }[] = [];
for (const id of selected) {
counter.comparisons++;
const idx = indexById.get(id);
if (idx !== undefined) picked.push({ idx, item: items[idx] });
}
picked.sort((a, b) => a.idx - b.idx);
return picked.map((p) => p.item);
};
}
describe('ResourceList selectedEntries projection (benchmark gate)', () => {
it('identical output (order + membership) and O(k) vs O(2N) comparisons per toggle', () => {
const N = 2000;
const items = buildItems(N);
const project = makeAfterProjector(items);
// Model a 50-item shift-range selection built one id at a time,
// re-deriving after each toggle (what the reactive graph does).
const selected = new Set<string>();
const beforeCounter = { comparisons: 0 };
const afterCounter = { comparisons: 0 };
// Insert in REVERSE order so selection order ≠ item order — the
// order-preservation gate below must still hold. Each toggle
// re-derives both shapes (what the reactive graph does).
for (let i = 149; i >= 100; i--) {
selected.add(`it-${i}`);
selectedBefore(items, selected, beforeCounter);
project(selected, afterCounter);
}
// Stale ids (deleted rows) must be dropped by both shapes.
selected.add('it-ghost');
const lastBefore = selectedBefore(items, selected, beforeCounter).component;
const lastAfter = project(selected, afterCounter);
expect(lastAfter).toEqual(lastBefore); // same entries, same (item) order
// BEFORE: 2 scans × N per toggle. AFTER: k probes per toggle.
expect(beforeCounter.comparisons).toBe(51 * 2 * N);
expect(afterCounter.comparisons).toBeLessThan(51 * 51 + 1);
});
it('wall clock: 500-toggle sweep on a 5k list is faster with the projection', () => {
const N = 5000;
const items = buildItems(N);
const project = makeAfterProjector(items);
const selected = new Set<string>();
const nul = { comparisons: 0 };
const t0 = performance.now();
for (let i = 0; i < 500; i++) {
selected.add(`it-${i}`);
selectedBefore(items, selected, nul);
}
const tBefore = performance.now() - t0;
selected.clear();
const t1 = performance.now();
for (let i = 0; i < 500; i++) {
selected.add(`it-${i}`);
project(selected, nul);
}
const tAfter = performance.now() - t1;
// Generous bound to keep CI stable; locally ~10-40x.
expect(tAfter).toBeLessThan(tBefore);
});
});
// ─── [2] Recent favorite-star dependency ────────────────────────────────────
interface RawItem {
id: string;
name: string;
}
/** BEFORE — mapper reads the favorite set: every toggle re-maps ALL rows. */
function entriesBefore(
raw: RawItem[],
favoriteIds: Set<string>,
counter: { mapperRows: number }
): { id: string; isFavorite: boolean }[] {
return raw.map((it) => {
counter.mapperRows++;
return { id: it.id, isFavorite: favoriteIds.has(it.id) };
});
}
/** AFTER — mapper is set-independent; the star widget reads membership. */
function entriesAfter(raw: RawItem[], counter: { mapperRows: number }): { id: string }[] {
return raw.map((it) => {
counter.mapperRows++;
return { id: it.id };
});
}
function starStateAfter(favoriteIds: Set<string>, id: string): boolean {
return favoriteIds.has(id);
}
describe('Recent favorite-star fine-grained dependency (benchmark gate)', () => {
it('a star toggle re-maps 0 rows (was N) and renders the same star states', () => {
const N = 400;
const raw: RawItem[] = Array.from({ length: N }, (_, i) => ({
id: `r-${i}`,
name: `File ${i}`
}));
const favoriteIds = new Set<string>(['r-3']);
const beforeCounter = { mapperRows: 0 };
const afterCounter = { mapperRows: 0 };
// Initial render: both shapes map all rows once.
let entriesB = entriesBefore(raw, favoriteIds, beforeCounter);
const entriesA = entriesAfter(raw, afterCounter);
expect(beforeCounter.mapperRows).toBe(N);
expect(afterCounter.mapperRows).toBe(N);
// 10 star toggles. BEFORE: the mapper depends on the set → full
// re-map each time. AFTER: the mapper doesn't run at all.
for (let k = 0; k < 10; k++) {
const id = `r-${k * 7}`;
if (favoriteIds.has(id)) favoriteIds.delete(id);
else favoriteIds.add(id);
entriesB = entriesBefore(raw, favoriteIds, beforeCounter); // reactive re-run
// AFTER: no mapper re-run; only the affected star re-reads.
starStateAfter(favoriteIds, id);
}
expect(beforeCounter.mapperRows).toBe(N + 10 * N);
expect(afterCounter.mapperRows).toBe(N); // unchanged since initial render
// Gate: identical star state for every row under the AFTER shape.
for (let i = 0; i < N; i++) {
expect(starStateAfter(favoriteIds, entriesA[i].id)).toBe(entriesB[i].isFavorite);
}
});
});
// ─── [3] admin timeAgo date fallback ────────────────────────────────────────
describe('admin timeAgo >30d fallback formatter cache (benchmark gate)', () => {
it('cached formatter output is identical to toLocaleDateString()', async () => {
const { dateTimeFormatFor } = await import('../utils/display');
const dates = [
new Date('2025-01-15T10:30:00Z'),
new Date('2024-12-31T23:59:59Z'),
new Date('2020-06-01T00:00:00Z'),
new Date('1999-02-28T12:00:00Z')
];
for (const d of dates) {
expect(dateTimeFormatFor(undefined).format(d)).toBe(d.toLocaleDateString());
}
});
it('1000 formats construct ≤1 Intl.DateTimeFormat (was 1000)', async () => {
const { dateTimeFormatFor } = await import('../utils/display');
const RealDTF = Intl.DateTimeFormat;
let constructed = 0;
// Count constructions through both paths.
const Counting = new Proxy(RealDTF, {
construct(target, args: [string?, Intl.DateTimeFormatOptions?]) {
constructed++;
return new target(...args);
}
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Intl as any).DateTimeFormat = Counting;
try {
const d = new Date('2020-06-01T00:00:00Z');
constructed = 0;
for (let i = 0; i < 1000; i++) {
d.toLocaleDateString();
}
// jsdom implements toLocaleDateString via Intl internally in some
// versions; count only if observable. The load-bearing assertion
// is the cached path below.
const beforeConstructed = constructed;
constructed = 0;
for (let i = 0; i < 1000; i++) {
dateTimeFormatFor(undefined).format(d);
}
expect(constructed).toBeLessThanOrEqual(1);
// When the environment exposes per-call constructions, require
// the cached path to be strictly cheaper.
if (beforeConstructed > 1) {
expect(constructed).toBeLessThan(beforeConstructed);
}
} finally {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(Intl as any).DateTimeFormat = RealDTF;
}
});
});