Avoid redundant blob publication indexing and restore capture pins

Merged
VA
8h ago
+177-4

Blob publication no longer builds an identity table for a batch whose object kinds already prove that its IDs are distinct. A one-chunk blob publishes a BlobChunk and a Blob page; those can never be equal ObjectIds. This removes #961's extra allocation and collision-dependent probes instead of accepting them into the performance expectations.

The original allocation and item pins are restored byte-for-byte: Linux owner capture 46 allocations; complete Linux rescan 331 allocations and 331 normalized items. Windows retains its original 43 / 324 / 436 pins. No operation-derived delta is added.

Why and where an index is needed

LogicalObjectStore::put_many and put_many_hashed are direct public batch boundaries, including callers without StagedObjects. The lower Objects provider processes repeated keys independently, returning a precondition failure for a later immutable insertion; it does not coalesce the filesystem batch or preserve its unique-write accounting. General batches therefore still need duplicate resolution before provider dispatch.

The publication choke point now records object-kind membership in four u64 words. A first occurrence cannot duplicate any preceding ID and requires no object-table probe. On the first repeated kind, publication creates the existing exact-layout index once and seeds it from the already-owned unique result list. Subsequent operations use that index. This preserves #961's linear indexed path for large same-kind batches without building a second table for structurally distinct chunk/page batches. Every performed hash-slot probe is still charged before access. All 256 possible canonical u8 tags fit the membership representation.

Guarantee

  • Class: H (repeated work/allocations), with I (accepting a regression into an oracle). The single private publication path admits an identity table only when kind membership can no longer establish uniqueness. Both original complete-operation pins remain unchanged.
  • Proof: exhaustive production-bound comparison for all 255 nonempty subsets of the eight object kinds; 32-case shrinking property for arbitrary kind order, duplicate identities, distinct same-kind objects, raw/authenticated input and object-size errors. Candidate and old eager path have separate stores and full body/error comparisons. Existing exact N/2N accounting, forced-collision denial-before-mutation, exact physical allocation/custody and cancellation properties remain. The eager-table control restores the old production constructor and insert loop; it must fail the same original rescan allocation pin, add charged items, and preserve complete output, errors and syscall trace. Existing stat fallback, quota, cancellation, owner-allocation and birth-time controls remain.
  • Entry points: raw put_many (ordinary and native original-custody), authenticated ordinary put_many_hashed, direct logical callers and the complete production native capture/rescan path. Authenticated native batches retain the existing typed unsupported boundary and its regression proof. No parser or concurrency primitive changes.
  • Gaps: none for the redundant-index class and these two main failures. All exact-head platform gates below passed.

Validation

Signed and pushed head: 17b60141d69e82e29298a1ca1ac66b6c48a32020. Worktree: Q:/wt/co-perf-main-counters.

PlatformFocused resultSlowest suiteBuild
Linux / bb216 passed; original 46 / 331 / 331 pins0.37 s7m27s
Windows native16 passed; original 43 / 324 / 436 pins0.09 s13m26s
macOS / ivar14 passed; all kind subsets and promotion/index properties0.05 s6m42s
Windows GNU / bb2lib/tests compile passed—1m08s

Every kind-disjoint batch has exactly 2 + 3*N logical allocation operations and zero identity probes, versus the independently initialized eager path's 3 + 3*N operations and nonzero probes. Complete native capture/rescan measures physical heap allocations as well as charged work. Larger repeated-kind batches retain the exact N/2N probe/allocation property and forced-collision controls.

Paired aggregate publication diagnostics for 255 batches (old/new): Linux 6.124268 / 6.206901 ms; macOS 4.822340 / 8.227502 ms; Windows isolated built-binary rerun 2.5566 / 2.5878 ms. Linux/macOS diagnostics ran alongside the other property tests; they establish no CPU-time speedup. Acceptance is the removed physical allocation and probes, unchanged complete outputs/errors/traces, and original exact pins. All suites stayed under 10 seconds.

macOS used /opt/homebrew/bin/sccache; all four lane-owned target/source/archive/driver paths were independently verified absent afterward (48 GiB free). No queued/running lane build remains. No scratch files are committed.

The unchanged-main production capture failures are recorded in Q:/lanes/main-gate/ws-32a9ae569.log: one extra physical allocation and normalized items 333/334 instead of 331. The focused filesystem tier caught the regression; my initial PR attempted to derive the added costs rather than remove them. This revision restores those tests and fixes publication.

Paired measurement command (all platforms): build cargo test --locked -p acyclic-fs --lib --features model-checking --no-run --message-format=json, then run the emitted binary directly with every_object_kind_subset_avoids_duplicate_index --nocapture. It prints aggregate old/new publication duration over 255 independently initialized paired batches and checks exact allocation/probe differences plus complete readback. Timings are debug-build diagnostics; exact physical/work pins are acceptance.

Focused gates: emitted binary filters native_capture::owner_allocation_tests::, native_capture::rescan_probe_tests::, logical_objects::tests::, logical_objects::index:: (each hard-bounded at 10 seconds). GNU compile: cargo check --locked -p acyclic-fs --lib --tests --features model-checking --target x86_64-pc-windows-gnu. macOS runs the logical/index suites with sccache; its own target and source/archive/driver are removed after the run.

Decisions

Restore the original pins and remove unconditional index work. Retain fully charged duplicate resolution for arbitrary direct batches; use ObjectId's existing kind discriminator rather than another allocation, an arbitrary small-batch cutoff or an unmetered linear duplicate scan. Own non-merge commits only; normal signed push.

Coordinator: existing acyclic-fs library qualification should include the four focused filters above; no new GitHub-hosted Rust job.