perf: serve ranges from RAM cache, stream ZIPs, overlap ingest settle, O(1) chunk gate

Round 2 of benchmark-gated optimizations (benches/ROUND2.md; every change
gated by a before/after in examples/bench_round2.rs — an AFTER that did
not beat its BEFORE was to be rolled back; none needed it):

- Range requests (REST/DAV/shares) answered from the moka content cache
  for sub-10MB files: PG resolve + open/seek/read -> Bytes::slice.
  256KiB seeks: 1,730/s -> 3.7M/s (p50 552us -> 0.15us).
- Streaming folder/share ZIPs via tokio duplex: TTFB no longer scales
  with archive size (326ms -> 0.4ms on 192MiB corpus; total also faster).
  Content-Length dropped (size unknown up front).
- NC chunked-upload per-PUT gate: O(k) directory scan+stat -> in-RAM
  per-session counter (lazy rebuild on cold start). 1,000-chunk upload
  gate cost: 33.1s -> 0.09s cumulative.
- Delta download + commit-verify now use the CDC path's
  buffered(read_prefetch) read-ahead: 64-chunk drain at 5ms open
  latency 440ms -> 51ms; order preserved.
- CDC ingest settles batches on a spawned task (depth-1 pipeline) so
  the source stream keeps flowing during PG pin + backend writes;
  rollback ledger shared + lock-serialized so compensation stays exact
  on cancellation. 512MiB paced ingest: 60-69 -> 74-75 MB/s.
  OXICLOUD_INGEST_OVERLAP=0 restores inline settling (ops/bench hatch).
- Frontend: instant-upload BLAKE3 hashing moved off the main thread to
  a bounded Web Worker pool (File handles by reference); vitest gate
  asserts the pool beats sequential (first gate draft posting buffers
  was 2.6x slower and was rewritten — copies dominated).

Validation: cargo fmt + clippy -D warnings clean; 514 unit + 544
integration tests green; 270 frontend tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBK1RdtzyP6759Muqe1K1w
This commit is contained in:
Claude
2026-07-16 16:50:07 +00:00
parent aba89c4f5d
commit 82ee7da0d2
18 changed files with 1245 additions and 195 deletions
@@ -19,7 +19,7 @@ use axum::{
response::{IntoResponse, Response},
};
use bytes::{Buf, Bytes, BytesMut};
use futures::Stream;
use futures::{Stream, TryStreamExt};
use std::sync::Arc;
use tokio_stream::StreamExt;
@@ -343,17 +343,36 @@ pub async fn delta_download_chunks(
// Stream the frames: 4-byte length headers come from the (entitled)
// index sizes; bytes stream straight from the blob backend. Peak RAM
// is one backend read frame, independent of batch size.
// is bounded by `read_prefetch` open streams (their first frame),
// independent of batch size.
//
// `buffered(read_prefetch)` overlaps the NEXT chunk's open with the
// current chunk's drain — the same combinator/tuning as the main CDC
// download path (benches/BLOB-PREFETCH.md). The old per-chunk await
// paid every open's full round-trip serially: on an object-store
// backend a 64-chunk batch at ~30 ms first-byte cost ~1.9 s of pure
// latency. Frames still arrive strictly in request order.
let prefetch = service.read_prefetch().max(1);
let svc = service.clone();
// `futures::StreamExt` spelled out — this handler imports
// `tokio_stream::StreamExt`, whose `map` adapter lacks `buffered`.
let opened = futures::StreamExt::map(futures::stream::iter(ordered), move |(hash, size)| {
let svc = svc.clone();
async move {
let chunk = svc
.chunk_stream(&hash)
.await
.map_err(std::io::Error::other)?;
let header = futures::stream::once(async move {
Ok::<Bytes, std::io::Error>(Bytes::copy_from_slice(&(size as u32).to_be_bytes()))
});
Ok::<_, std::io::Error>(futures::StreamExt::chain(header, chunk))
}
});
let body_stream: std::pin::Pin<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>> =
Box::pin(async_stream::try_stream! {
for (hash, size) in ordered {
yield Bytes::copy_from_slice(&(size as u32).to_be_bytes());
let mut chunk = service.chunk_stream(&hash).await.map_err(std::io::Error::other)?;
while let Some(part) = chunk.next().await {
yield part?;
}
}
});
Box::pin(TryStreamExt::try_flatten(futures::StreamExt::buffered(
opened, prefetch,
)));
Ok(Response::builder()
.status(StatusCode::OK)
.header(header::CONTENT_TYPE, "application/octet-stream")