perf: keyset/LATERAL SQL shapes, auth+blob-cache single-flight, spool buffers, DTO interning

Round 3 of benchmark-gated optimizations (benches/ROUND3.md; every change
gated by a before/after benchmark — an AFTER that did not beat its BEFORE
was to be rolled back; none needed it. Equivalence gates assert identical
row sequences / byte-identical output on every behavior-preserving rewrite):

DB hot paths (local PG16, EXPLAIN-verified):
- Web-UI listing (list_resources_paged): cursor pushed INSIDE the
  folders/files UNION-ALL branches as sargable row-value comparisons with
  per-branch ORDER/LIMIT + two partial expression indexes
  (folder_id, LOWER(name), id). 20k-entry folder: 26.6 -> 1.3 ms/page
  (19.5x); other sort modes at parity or better. New migration
  20260918000000. [benches/LISTING-KEYSET.md section in ROUND3]
- Photos timeline (list_media_files): per-drive CROSS JOIN LATERAL top-N
  on the timeline index, joins moved above the top-N. 50k-photo library:
  97.4 -> 1.6 ms/page (55.7x). The old "LIMIT stops the scan early"
  comment was refuted by EXPLAIN.
- PROPFIND sub-folders (both DAV surfaces): keyset list_folders_batch off
  idx_folders_unique_name replaces COUNT(*) OVER() + LIMIT/OFFSET
  (5k dirs: 79.7 -> 17.9 ms full walk, 4.5x).

Concurrency:
- Basic-auth cache single-flight (moka try_get_with): 8 concurrent DAV
  connections at TTL expiry paid 8 Argon2id runs (2.6 s CPU + 8x64 MiB);
  now 1 (300 ms). Failed verifications remain uncached.
- CachedBlobBackend per-hash single-flight + unique tmp names: 16
  concurrent cold readers = 16 full remote downloads racing truncating
  writes on ONE deterministic .tmp (corruptible cache); now 1 download
  (16x less egress, 2.8x wall on a shared link) and torn files can never
  be renamed into the cache.

I/O and allocations:
- Chunk-assembly reads 64K -> 512K buffers (2.3x, 8x fewer syscalls);
  chunk-spool writes via BufWriter 512K (5.6x, 32x fewer syscalls).
- S3/Azure put_blob_from_bytes_unsynced overrides: dedup settle no longer
  pays a HEAD probe per new chunk (2 RTT -> 1, 1.8x); Azure stops copying
  every chunk (Bytes -> Body, -0.44 ms - 4 MiB alloc per 4 MiB chunk).
- Entity->DTO mapping: Arc<str> interning of closed-set display fields +
  common MIMEs, 1-alloc etag/size formatting, FolderDto moves instead of
  clones. File row: 11 -> 4 allocs; folder row: 11.8 -> 1 (2.1x faster).
- CardDAV REPORT: deleted dead per-contact vCard pre-generation and the
  O(N^2) uid scan whose result was discarded (5k contacts: 55.7 -> 5.7 ms,
  9.8x); byte-identical XML asserted.
- Search-results cache: byte weigher + 32 MiB budget
  (OXICLOUD_SEARCH_CACHE_MAX_BYTES) replaces the 1000-ENTRY cap that let
  ~300 MiB of enriched rows sit in RSS; read latency parity.
- Dropped aws-config + aws-smithy-types (zero references; -82 dep-graph
  nodes, three SDK stacks gone from every build). tokio "process" is now
  an explicit feature (was enabled transitively by aws-config).

Frontend:
- Cached Intl.DateTimeFormat keyed by (locale, options) in formatDate and
  4 sibling callsites: 20k dates 2612 -> 51 ms (51.6x); vitest gate
  asserts output identity across locales and a 3x floor.

Validation: cargo fmt + clippy --all-features --all-targets -D warnings
clean; 518 unit + 548 integration-cfg tests green; new-shape endpoints
smoke-tested end-to-end over HTTP (all 5 listing sort modes with cursor
walks, WebDAV PROPFIND Depth-1, photos timeline, Basic-auth DAV login);
frontend npm run check clean, new vitest gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBsU2qEzny3A8WQUEuMNCr
This commit is contained in:
Claude
2026-07-17 11:10:27 +00:00
parent 7d95a19907
commit cd4c62042a
43 changed files with 5290 additions and 439 deletions
+55 -1
View File
@@ -56,6 +56,60 @@ export function fileIconKindClass(iconName: string): string {
return `file-icon--${fileIconKind(iconName)}`;
}
/**
* Module-scope cache of `Intl.DateTimeFormat` instances, keyed by
* `(locale, options signature)`. Constructing a formatter runs the full ICU
* locale/pattern resolution (~50–200µs) while a `format()` call is ~1µs, and
* {@link formatDate} runs roughly twice per row as large file lists render
* and scroll — so a construct-per-call implementation (what
* `toLocaleDateString(locale, options)` does under the hood) dominated list
* fill. Entries are keyed by the locale actually requested — never frozen at
* first use — so a runtime locale change just resolves a different entry.
*/
const dateTimeFormatCache = new Map<string, Intl.DateTimeFormat>();
// Entries built with `locale === undefined` snapshot the environment default
// locale at construction time. `toLocaleDateString(undefined, …)` re-reads the
// default on every call, so drop the cache if the default changes to keep the
// cached path behaviourally identical.
if (typeof window !== 'undefined') {
window.addEventListener('languagechange', () => dateTimeFormatCache.clear());
}
/**
* Cached equivalent of `new Intl.DateTimeFormat(locale, options)`.
*
* `date.toLocaleDateString(locale, options)` / `toLocaleTimeString(…)` are
* specified (ECMA-402) as building exactly this formatter per call — and
* their component defaulting is a no-op once `options` names any date/time
* component — so `dateTimeFormatFor(locale, options).format(date)` is
* output-identical while paying construction once per (locale, options).
*
* The options signature uses `JSON.stringify`, so pass options as a hoisted
* const or an inline literal (stable key order per callsite); a differently
* ordered but equal object would only create a redundant entry, never a wrong
* result.
*/
export function dateTimeFormatFor(
locale: string | undefined,
options?: Intl.DateTimeFormatOptions
): Intl.DateTimeFormat {
const key = `${locale ?? ''}|${options ? JSON.stringify(options) : ''}`;
let fmt = dateTimeFormatCache.get(key);
if (!fmt) {
fmt = new Intl.DateTimeFormat(locale, options);
dateTimeFormatCache.set(key, fmt);
}
return fmt;
}
/** Options for {@link formatDate}, hoisted so every call shares one cache key. */
const FORMAT_DATE_OPTS: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: 'numeric'
};
/** Format a timestamp (epoch seconds/ms or ISO-8601 string) as a local date. */
export function formatDate(value: number | string | null | undefined): string {
if (value === null || value === undefined) return '';
@@ -67,5 +121,5 @@ export function formatDate(value: number | string | null | undefined): string {
d = new Date(value);
}
if (Number.isNaN(d.getTime())) return '';
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
return dateTimeFormatFor(undefined, FORMAT_DATE_OPTS).format(d);
}
@@ -0,0 +1,177 @@
import { describe, expect, it } from 'vitest';
import { dateTimeFormatFor, formatDate } from './display';
/**
* Benchmark gate for the module-scope `Intl.DateTimeFormat` cache in
* `display.ts` ({@link formatDate} / {@link dateTimeFormatFor}).
*
* Audit finding: `formatDate` built a fresh `Intl.DateTimeFormat` on every
* call (`toLocaleDateString(undefined, opts)` constructs one internally), and
* it runs ~twice per row while file lists render and scroll — a 10k-item
* folder paid tens of thousands of ICU formatter constructions (~50–200µs
* each) during list fill. The fix caches formatters in a Map keyed by
* (locale, options signature).
*
* This gate asserts (1) the cached path is byte-identical to the
* construct-per-call code it replaced, across dates, option shapes, and
* locales (including an RTL one), and (2) it is decisively (≥3x) faster. If
* the perf assertion fails, the cache is not delivering and the change
* should be rolled back (it would be pure complexity).
*/
/** The option shapes the app actually uses (display.ts + component callsites). */
const DATE_OPTS: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' };
const MONTH_OPTS: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long' };
const FULL_DATE_OPTS: Intl.DateTimeFormatOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
const DATE_TIME_OPTS: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
const TIME_OPTS: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' };
/**
* The pre-fix `formatDate`, verbatim: `toLocaleDateString` constructs a new
* `Intl.DateTimeFormat` internally on every call. This is the uncached
* reference the cached implementation must match and beat.
*/
function referenceFormatDate(value: number | string | null | undefined): string {
if (value === null || value === undefined) return '';
let d: Date;
if (typeof value === 'number') {
// Heuristic: seconds vs milliseconds.
d = new Date(value < 1e12 ? value * 1000 : value);
} else {
d = new Date(value);
}
if (Number.isNaN(d.getTime())) return '';
return d.toLocaleDateString(undefined, DATE_OPTS);
}
/** ~20 inputs exercising the seconds/ms heuristic, ISO parsing, and edge cases. */
const DATE_VALUES: Array<number | string | null | undefined> = [
0, // epoch, seconds branch
1, // seconds
86_399, // seconds, last second of 1970-01-01 UTC
951_782_400, // seconds, 2000-02-29 (leap day)
1_700_000_000, // seconds
999_999_999_999, // just under the 1e12 cutoff → seconds branch, far future
1_000_000_000_000, // exactly 1e12 → milliseconds branch, 2001
1_700_000_000_000, // milliseconds
1_766_620_800_000, // milliseconds, 2025-12-25
Date.UTC(1999, 11, 31, 23, 59, 59), // ms, century boundary
Date.UTC(2038, 0, 19, 3, 14, 7), // ms, past the 32-bit epoch rollover
'2024-01-15', // date-only ISO (parsed as UTC midnight)
'2024-02-29T12:34:56Z', // leap day, UTC
'1999-12-31T23:59:59.999Z',
'2020-06-15T10:00:00+05:30', // non-UTC offset
'2031-11-05T08:15:30-05:00',
'0001-01-01T00:00:00Z', // extreme past
'2024-07-04T00:00:00', // no offset (local time)
'definitely not a date', // invalid → ''
'', // invalid → ''
null, // → ''
undefined // → ''
];
/** Locales the app ships (see SUPPORTED_LOCALES); 'ar' renders RTL. */
const SAMPLE_LOCALES = ['en', 'es', 'ar', 'ja'] as const;
describe('cached Intl.DateTimeFormat (benchmark gate)', () => {
it('formatDate output is identical to the uncached reference', () => {
for (const value of DATE_VALUES) {
expect(formatDate(value), `formatDate(${JSON.stringify(value)})`).toBe(
referenceFormatDate(value)
);
}
});
it('cached formatters match per-call construction across locales and option shapes', () => {
const dates = DATE_VALUES.filter((v): v is number | string => v !== null && v !== undefined)
.map((v) => (typeof v === 'number' ? new Date(v < 1e12 ? v * 1000 : v) : new Date(v)))
.filter((d) => !Number.isNaN(d.getTime()));
expect(dates.length).toBeGreaterThanOrEqual(18);
for (const locale of SAMPLE_LOCALES) {
for (const d of dates) {
// Each toLocale*String call below is specified as constructing a
// fresh Intl.DateTimeFormat — the uncached reference behaviour.
expect(dateTimeFormatFor(locale, DATE_OPTS).format(d)).toBe(
d.toLocaleDateString(locale, DATE_OPTS)
);
expect(dateTimeFormatFor(locale, MONTH_OPTS).format(d)).toBe(
d.toLocaleDateString(locale, MONTH_OPTS)
);
expect(dateTimeFormatFor(locale, FULL_DATE_OPTS).format(d)).toBe(
d.toLocaleDateString(locale, FULL_DATE_OPTS)
);
expect(dateTimeFormatFor(locale, DATE_TIME_OPTS).format(d)).toBe(
d.toLocaleDateString(locale, DATE_TIME_OPTS)
);
expect(dateTimeFormatFor(locale, TIME_OPTS).format(d)).toBe(
d.toLocaleTimeString(locale, TIME_OPTS)
);
expect(dateTimeFormatFor(undefined, DATE_OPTS).format(d)).toBe(
d.toLocaleDateString(undefined, DATE_OPTS)
);
}
}
});
it('reuses one instance per (locale, options) and never freezes the first locale', () => {
// Same key → same instance (this is where the speedup comes from).
expect(dateTimeFormatFor('es', DATE_OPTS)).toBe(dateTimeFormatFor('es', DATE_OPTS));
expect(dateTimeFormatFor(undefined, DATE_OPTS)).toBe(dateTimeFormatFor(undefined, DATE_OPTS));
// Different locale or options → different instance: a runtime locale
// change must not keep formatting with the first locale seen.
expect(dateTimeFormatFor('ar', DATE_OPTS)).not.toBe(dateTimeFormatFor('es', DATE_OPTS));
expect(dateTimeFormatFor('es', TIME_OPTS)).not.toBe(dateTimeFormatFor('es', DATE_OPTS));
const d = new Date(Date.UTC(2024, 4, 17, 12, 0, 0));
expect(dateTimeFormatFor('ar', DATE_OPTS).format(d)).toBe(
d.toLocaleDateString('ar', DATE_OPTS)
);
expect(dateTimeFormatFor('es', DATE_OPTS).format(d)).toBe(
d.toLocaleDateString('es', DATE_OPTS)
);
});
it(
'formats 20k dates ≥3x faster than per-call construction (perf gate)',
{ timeout: 30_000 },
() => {
const N = 20_000;
const base = Date.UTC(2020, 0, 1);
// Deterministic spread of distinct ms timestamps across ~30 years.
const values = Array.from({ length: N }, (_, i) => base + i * 47_777_777);
// Warm up both paths so JIT tiering and first-call construction sit
// outside the measured windows. `sink` defeats dead-code elimination.
let sink = 0;
for (let i = 0; i < 500; i++) {
sink += formatDate(values[i]).length;
sink += referenceFormatDate(values[i]).length;
}
const t0 = performance.now();
for (const v of values) sink += formatDate(v).length;
const cachedMs = performance.now() - t0;
const t1 = performance.now();
for (const v of values) sink += referenceFormatDate(v).length;
const uncachedMs = performance.now() - t1;
expect(sink).toBeGreaterThan(0);
console.info(
`formatDate x ${N}: cached ${cachedMs.toFixed(1)} ms vs construct-per-call ${uncachedMs.toFixed(1)} ms (${(uncachedMs / cachedMs).toFixed(1)}x)`
);
expect(cachedMs).toBeLessThan(uncachedMs / 3);
}
);
});