Seven behaviour-preserving allocation / copy / bandwidth cuts, each behind a counting-allocator BEFORE/AFTER gate that exit(1)s unless AFTER allocates strictly fewer than BEFORE (benches/ROUND29.md, examples/bench_round29_micro.rs). - [B] Content-cache serve fast path (optimized_inner Tier 1 + get_file_range_preloaded — the video-scrub hot path): probe the cache with a borrow first and build the owned get_or_load args (quoted-etag / key / id Strings) only on a miss, instead of allocating them before every probe and discarding them on a hit. 6 -> 0 allocs per cache hit. Splits get_or_load into get + load_and_cache so the miss path is not re-probed and the hit/miss stat counters stay byte-identical. Also drops the unconditional content_hash/name clones that ran for the >=10 MB streaming tier that used neither. - [A] NextCloud REPORT emit loops: per-row href String (and format! per folder row) -> one reused href_buf via nc_href_into / nc_collection_href_into with the URL-encoded user computed once per page. 1497 fewer allocs on a 500-row page. - [C] read_full: a single-frame blob is returned zero-copy instead of a second whole-payload memcpy into a fresh BytesMut; multi-frame path unchanged. - [D] login-lockout key: to_lowercase()+format! -> one pre-sized ASCII buffer (non-ASCII keeps str::to_lowercase). 3 -> 1 alloc/req, byte-identical key. - [E] NC composite-username parse: owned clone/to_string -> &str borrow of the already-owned raw_username. 1 -> 0 alloc on the common no-marker path. - [F] get_contacts_in_group: stop SELECTing the discarded multi-KB vcard column (the live method ROUND25 §Q2 missed; ContactDto has no vcard field). - [G] count_admin_users: add count_users_by_role -> scalar COUNT(*) instead of hydrating every admin's full row (incl. up-to-512 KiB avatar + ui_preferences JSONB) only to .len() it, on a bootstrap-polled status endpoint. All seven gates pass; cargo fmt --check and cargo clippy --all-features --all-targets -D warnings clean. §F/§G additionally validated against a live PostgreSQL 16 with the full migration set (query validity, result equivalence, 600000 -> 8 byte wire delta on the admin count). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LhpDZxSQTAGnAqCHUdtG5N
13 KiB
Round 29 — read-path cache-serve allocs, NC REPORT href buffer, auth per-request allocs, DB over-fetch
Seven behaviour-preserving cuts, each behind a counting-allocator BEFORE/AFTER gate
that exit(1)s (GATE FAIL … rollback) unless AFTER allocates strictly fewer than
BEFORE. Sections span the four hot paths a deep re-audit surfaced that the prior 28
rounds had not reached: the content-cache serve fast path (video scrubbing), the
NextCloud REPORT emit loops, the NextCloud Basic-Auth request path, and two
Postgres over-fetch sites.
Reproduce:
RUSTFLAGS="-C target-cpu=x86-64-v3" \
cargo run --release --features bench --example bench_round29_micro
| § | site | allocs/op BEFORE→AFTER | wall |
|---|---|---|---|
| A | NC REPORT href reused buffer | 3500 → 2003 /500-row page | 1.59× |
| B | cache-serve borrow-probe (video scrub) | 6 → 0 /cache hit | 5.85× |
| C | read_full single-frame zero-copy |
1 → 0 | 194× |
| D | login-lockout single-alloc key | 3 → 1 | 2.10× |
| E | NC composite-username parse borrow | 1 → 0 | 2.86× |
| F | contact-group vcard over-fetch |
200 → 0 /200-row page | decode-shape |
| G | admin-count COUNT(*) vs hydrate |
25 → 0 /poll | decode-shape |
[B] Content-cache serve fast path: eager owned args built before the borrow-probe (HIGHEST — the hottest read path)
file_retrieval_service::optimized_inner (Tier 1) and get_file_range_preloaded
built the owned get_or_load arguments — format!("\"{}\"", hash) (the quoted
etag), hash.to_string() (the cache key), id.to_string() — before the cache
was probed. But FileContentCache::get_or_load's first line is a lock-free
self.get(&cache_key) that returns on a hit and never touches etag / ct / the
load closure. So every cache hit — the steady state of a repeat download and of
a range-seek storm (video scrubbing hits get_file_range_preloaded on every
seek) — allocated ~3–6 Strings and immediately dropped them. The returned etag/ct
are discarded by both callers (let (bytes, ..)), and the response etag is built
independently from file_dto.etag, so the eager etag was dead on the miss path too.
AFTER probes cache.get(&hash) (a borrow, zero owned allocs) first and slices on a
hit; only a miss builds the owned args and calls the new load_and_cache.
get_or_load is split into get + load_and_cache (it now composes them), so the
miss path is not re-probed — the hit/miss stat counters stay byte-identical to a
single get_or_load call. Also folds in the removal of the unconditional
content_hash.clone() + name.clone() that ran for every request including the
≥10 MB streaming tier that used neither.
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 241.1 | 6.00 |
| AFTER | 41.2 | 0.00 |
6 → 0 allocs per cache hit, 5.85× wall. On a 200-seek video scrub this removes
~1200 throwaway allocations. Equivalence: same cached Bytes returned; the split
preserves the exact single-get stat accounting.
[A] NextCloud REPORT emit loops: per-row href String → one reused buffer + once-encoded user
The two REPORT handlers (report_handler: favorites filter-files + search)
each emit a file loop and a folder loop that built <d:href> per row with
nc_href(url_user, subpath) — a fresh String per file row — and
format!("{}/", nc_href(...)) — two Strings per folder row — while re-encoding
the constant url_user on every row. The hotter PROPFIND child loop was already
hoisted to a reused buffer + once-encoded prefix (ROUND19/27); the REPORT loops were
the last per-row href allocation on the NC emit surface (the ROUND20/27/28 deferred
item). AFTER adds nc_href_into / nc_collection_href_into (the 0-alloc,
write-into-a-buffer form; nc_href/nc_collection_href now delegate to them, no
duplication) and computes into one href_buf reused across both loops with the
encoded_user computed once per page.
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 115 849.2 | 3500.00 |
| AFTER | 72 835.0 | 2003.00 |
1497 fewer allocs on a 500-row page, 1.59× wall. The 2003 residual is the
per-segment urlencoding::encode (4 path segments/row) that AFTER keeps to stay
byte-identical; the win is the removed per-row href String, the folder format!,
and the per-row user encode. Equivalence: AFTER href bytes match BEFORE
(file + folder) across a matrix of paths.
[C] read_full: single-frame blob no longer double-copied
read_full reassembled the blob stream with BytesMut::with_capacity(cap) +
extend_from_slice per frame. The local backend yields owned contiguous Bytes
frames, and a sub-CACHE_THRESHOLD blob arrives as exactly one frame — yet the
old code copied that whole payload a second time into a fresh buffer (a full-payload
memcpy + a BytesMut alloc) for every small cacheable download and every
uncacheable small read. AFTER returns the sole frame directly; only a multi-frame
read pays the pre-sized concat (byte-identical).
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 3325.9 | 1.00 |
| AFTER | 17.2 | 0.00 |
1 → 0 allocs and one 200 KB memcpy removed (194× wall on the isolated copy).
Equivalence: identical Bytes out; multi-frame path unchanged.
[D] NextCloud login-lockout key: to_lowercase() + format! → one ASCII buffer
LoginLockoutService::key built the composite (account, IP) cache key with
format!("{}|{}", username.to_lowercase(), client_ip) — two heap allocations — on
every NC request (the check on the way in; a hit on the happy path is a lockout
miss). App passwords authenticate with an already-lowercase ASCII username in ~all
traffic, so AFTER renders the lowercased key into one pre-sized buffer for the ASCII
case and keeps str::to_lowercase only on the rare non-ASCII branch (exact Unicode,
e.g. final-sigma, semantics).
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 95.7 | 3.00 |
| AFTER | 45.7 | 1.00 |
3 → 1 alloc, 2.10× wall. Byte-identical key verified across {ASCII lower,
mixed-case, composite ~ marker, IPv4, IPv6, non-ASCII, unknown}. The lockout
decision (same key bytes, threshold, TTL) is unchanged; failed verifications still
bypass the cache and pay full Argon2.
[E] NextCloud composite-username parse: owned clone → borrow
The {username}~{drive_marker} split allocated the prefix per request —
raw_username.clone() on the common no-marker path (a full duplicate),
u.to_string() + m.to_string() on the marker path — even though username is
only ever passed by reference and raw_username outlives every use before it moves
into NcSession. AFTER borrows &str slices out of the already-owned
raw_username.
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 22.5 | 1.00 |
| AFTER | 7.9 | 0.00 |
1 → 0 allocs on the common DAV path, 2.86× wall. Stacks with §D on the same per-request surface. Byte-identical inputs reach every downstream call.
[F] Contact-group listing: stop fetching the multi-KB vcard only to drop it
contact_group_pg_repository::get_contacts_in_group SELECTed c.vcard — the full
serialized vCard TEXT with an embedded base64 PHOTO, the largest column — and
decoded it into a String per contact, but its sole live caller
(list_contacts_in_group) maps every row to ContactDto, which has no vcard
field, so it was fetched, shipped, decoded, and dropped. This is the ROUND25 §Q2
row_to_contact_lite treatment applied to the live group method this time (Q2
shipped it to get_contacts_by_group, which has zero call sites). AFTER omits the
column and passes String::new().
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 70 121.1 | 200.00 |
| AFTER | 1.4 | 0.00 |
The micro isolates the discarded-String decode (200 rows × 8 KiB): 200 → 0
per-row allocs. The new SQL was run against a live schema (all columns resolve,
join valid, empty and populated results correct); ContactDto output is
byte-identical. Same unit economics ROUND25 §Q2 measured (6.4× wall on 1000 ×
8 KiB vCards). Bandwidth win scales with the embedded-photo size.
[G] admin-user count: hydrate every full row → scalar COUNT(*)
count_admin_users (the system-status / initialization endpoint, polled at
bootstrap / login-page render) called list_users_by_role("admin").len(), fetching
every admin's full 21-column row — including the up-to-512 KiB avatar image data
URI and the ui_preferences JSONB (decoded into a discarded serde_json::Value
DOM) — only to take the length. AFTER adds count_users_by_role →
SELECT COUNT(*) … WHERE role::text = $1 through the existing domain-trait / port
delegation pattern.
| arm | ns/op | allocs/op |
|---|---|---|
| BEFORE | 12 634.9 | 25.00 |
| AFTER | 0.7 | 0.00 |
The micro isolates the hydrate-N-rows-then-len cost (3 admins × a 64 KiB avatar +
JSONB DOM): 25 → 0 allocs. Validated on a live DB with 3 seeded admins carrying
200 KiB avatars: the COUNT(*) returns the correct 3 while the wire payload drops
from 600 000 bytes (the three avatars) + JSONB to 8 bytes, and the app
hydrates zero User structs. Win scales with admin count, avatar size, and
PG-connection distance.
Not shipped — carried forward
Concrete, still-valuable items surfaced by the same re-audit, deferred here because they need a fixture this round can't drive, a structural change wider than an allocation cut, or a live-DB validation harness:
- Delta
store_loose_chunkscheck-then-write (highest-value dedup item). The delta upload path writes every received chunk to the backend unconditionally, then registers withON CONFLICT DO NOTHING— unlike the mainsettle_batchingest, which runs oneWHERE hash = ANY($1)existence probe per batch and writes only absent chunks (the discipline the S3 backend's dropped-HEAD comment already assumes). Bringing the delta path to parity eliminates redundant disk writes / object-store PUTs for content the server already has (multi-tenant overlap, abandoned-upload orphan re-sends). Deferred: near-zero on a single-tenant local server (the highest win is on S3/Azure), it restructures the ingest, and its gate is a backend-write-count harness (not the allocator), so it wants its own pass. The frontend already negotiates a Dropbox-style batched have/need exchange, so the client does not re-upload content the server has — this is purely the server-side write. ingest_chunks_from_streamend-of-stream reshape move. The final chunk registration clones every newly-written 64-byte hash to reshape forsync_blobs+ the UNNEST bind (~4000 String allocs on a 1 GB upload); the sibling sites were converted tointo_iter().unzip()moves in ROUND23/25 but this one wasn't. Deferred:st.writtenmust be restored on the two fallible error paths beforeguard.rollback()(which itselfmem::takes it), so the move needs arollback_with_writtenvariant — error-path surgery on the ingest correctness path for a once-per-upload (not per-frame) alloc cut.- NC
parse_basic_authcredential borrow. The shared helper returns(String, String)via twoto_string()s; the native Basic path already handscredentials.split_once(':')&strborrows toverify_basic_auth. Bringing NC to parity removes 2 allocs/request but touches a unit-tested shared helper and wants adecode_basic_credentialsextraction to avoid a third copy of the base64 logic. - DB
create_folder2 round-trips → 1INSERT … SELECT … RETURNING(the drive_id is a pure function of the parent;move_folderalready folds this). Needs theRowNotFound → not_foundbranch and a live-DB gate on the dup-name / missing-parent outcomes. - DB
list_users/search_userslite SELECT (droppassword_hash+ui_preferences, neither inUserDto) and contacts(address_book_id, full_name, first_name, last_name)composite index for the paginatedORDER BY. - File-metadata short-TTL cache so a range-seek storm stops re-
SELECTing the whole file row after the first seek (ROUND7 removed the per-seek authz; the metadata read remains). A genuinely new cache + write-invalidation wiring — its own validated pass. - S3 read zero-copy forward and the encrypted
PLAINTEXT_EMIT_SIZEbump — the ROUND25–28 carried-forward items needing MinIO / real-backend fixtures.
Environment / methodology
- Counting global allocator (
examples/bench_round29_micro.rs), no Postgres for the gate. Each section is BEFORE (replica of the shipped-before shape) vs AFTER (replica of the shipped-after shape, which the source now matches) with a value-equivalence assertion and aGATE FAIL … rollbackexit(1)if AFTER doesn't allocate fewer than BEFORE. §F and §G additionally validated against a live PostgreSQL 16 with the full migration set applied and a seeded fixture (query validity, result equivalence, wire-byte delta). - Built with
RUSTFLAGS="-C target-cpu=x86-64-v3"(the checked-in.cargo/config.tomlpinstarget-cpu=native, whichSIGILLs on this host). - Verified beyond the bench:
cargo fmt --all --checkclean,cargo clippy --all-features --all-targets -- -D warningsclean,cargo test --libgreen.