perf: round 21 — CalDAV/CardDAV row-mapper pre-size, dedup hash-bind & digest-key dedup, CardDAV etag/BDAY emit, NC trashbin content-type
Round 21 of the benchmark-gated perf sweep. Six behaviour-preserving, allocation-reducing changes, each with a BEFORE/AFTER counting-allocator section in examples/bench_round21_micro.rs and a byte/-value equivalence gate; all six pass their deterministic alloc gate (a non-winning AFTER exits 1 = rollback). - R1: pre-size the 16 CalDAV/CardDAV row-mapper Vecs (+1 HashMap) with Vec::with_capacity(rows.len()) — the ROUND20 §I1 file-side pattern extended to the calendar/contact repos it deferred. 7 → 1 allocs/op. - R2: settle_batch binds a borrowed Vec<&str> instead of cloning every chunk hash into a Vec<String> (sqlx encodes &[&str] as text[] identically; favorites_pg_repository.rs:271 precedent). 33 → 1 allocs/op, 39x wall. - R3: store_loose_chunks keys its intra-request dedup set on the raw [u8;32] BLAKE3 digest and moves the hex on a duplicate (the ROUND17 §D2 pattern applied to the delta-upload sibling). 401 → 209 allocs/op. - R4: CardDAV getetag emits borrowed pre-escaped " quotes via a shared write_quoted_etag helper (ROUND20 §C1 pattern, all 4 CardDAV etag sites). 3 → 0 allocs/op. - R5: BDAY stamped via the new fmt::compact_date stack renderer instead of chrono's strftime interpreter (chrono fallback out of the 4-digit-year range; byte-identical, unit-tested vs chrono). 2 → 0 allocs/op, 10.5x wall. - R6: NC trashbin folder content-type via Cow::Borrowed instead of .to_string() on the constant (ROUND16 §M1 pattern). 1 → 0 allocs/op. See benches/ROUND21.md for the full write-up and the deferred-items list (HeaderMap-clone hot handlers, Query→typed-struct, WebDAV dead-props HashSet, and others surfaced by the audit that want their own validated pass). Validated: cargo fmt, cargo clippy --features bench --all-targets -D warnings, cargo test --lib --features test_utils (529 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015gHVq5Wy2TzdWeSqtEmK6m
This commit is contained in:
@@ -182,7 +182,7 @@ impl CalendarEventRepository for CalendarEventPgRepository {
|
||||
DomainError::database_error(format!("Failed to get events in time range: {}", e))
|
||||
})?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mut event = CalendarEvent::with_id(
|
||||
row.get("id"),
|
||||
@@ -288,7 +288,7 @@ impl CalendarEventRepository for CalendarEventPgRepository {
|
||||
DomainError::database_error(format!("Failed to get events by calendar: {}", e))
|
||||
})?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mut event = CalendarEvent::with_id(
|
||||
row.get("id"),
|
||||
@@ -341,7 +341,7 @@ impl CalendarEventRepository for CalendarEventPgRepository {
|
||||
DomainError::database_error(format!("Failed to find events by summary: {}", e))
|
||||
})?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mut event = CalendarEvent::with_id(
|
||||
row.get("id"),
|
||||
@@ -515,7 +515,7 @@ impl CalendarEventRepository for CalendarEventPgRepository {
|
||||
DomainError::database_error(format!("Failed to get calendar events by UIDs: {}", e))
|
||||
})?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let mut event = CalendarEvent::with_id(
|
||||
row.get("id"),
|
||||
@@ -664,7 +664,7 @@ impl CalendarEventRepository for CalendarEventPgRepository {
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let event = CalendarEvent::with_id(
|
||||
row.get("id"),
|
||||
@@ -719,7 +719,7 @@ impl CalendarEventRepository for CalendarEventPgRepository {
|
||||
DomainError::database_error(format!("Failed to find recurring events in range: {}", e))
|
||||
})?;
|
||||
|
||||
let mut events = Vec::new();
|
||||
let mut events = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let event = CalendarEvent::with_id(
|
||||
row.get("id"),
|
||||
|
||||
@@ -211,7 +211,7 @@ impl CalendarRepository for CalendarPgRepository {
|
||||
DomainError::database_error(format!("Failed to get calendars by owner: {}", e))
|
||||
})?;
|
||||
|
||||
let mut calendars = Vec::new();
|
||||
let mut calendars = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let calendar = Calendar::with_id(
|
||||
row.get("id"),
|
||||
@@ -292,7 +292,7 @@ impl CalendarRepository for CalendarPgRepository {
|
||||
DomainError::database_error(format!("Failed to get public calendars: {}", e))
|
||||
})?;
|
||||
|
||||
let mut calendars = Vec::new();
|
||||
let mut calendars = Vec::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
let calendar = Calendar::with_id(
|
||||
row.get("id"),
|
||||
@@ -400,7 +400,7 @@ impl CalendarRepository for CalendarPgRepository {
|
||||
DomainError::database_error(format!("Failed to get calendar properties: {}", e))
|
||||
})?;
|
||||
|
||||
let mut properties = std::collections::HashMap::new();
|
||||
let mut properties = std::collections::HashMap::with_capacity(rows.len());
|
||||
for row in rows {
|
||||
properties.insert(row.get("name"), row.get("value"));
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ impl ContactGroupRepository for ContactGroupPgRepository {
|
||||
)
|
||||
})?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
let email_json: JsonValue = row.get("email");
|
||||
let phone_json: JsonValue = row.get("phone");
|
||||
|
||||
@@ -271,7 +271,7 @@ impl ContactRepository for ContactPgRepository {
|
||||
DomainError::database_error(format!("Failed to get contacts by uids: {}", e))
|
||||
})?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
contacts.push(Self::row_to_contact(row)?);
|
||||
}
|
||||
@@ -339,7 +339,7 @@ impl ContactRepository for ContactPgRepository {
|
||||
DomainError::database_error(format!("Failed to get contacts by address book: {}", e))
|
||||
})?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
contacts.push(Self::row_to_contact(row)?);
|
||||
}
|
||||
@@ -376,7 +376,7 @@ impl ContactRepository for ContactPgRepository {
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
contacts.push(Self::row_to_contact(row)?);
|
||||
}
|
||||
@@ -404,7 +404,7 @@ impl ContactRepository for ContactPgRepository {
|
||||
DomainError::database_error(format!("Failed to get contacts by email: {}", e))
|
||||
})?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
contacts.push(Self::row_to_contact(row)?);
|
||||
}
|
||||
@@ -434,7 +434,7 @@ impl ContactRepository for ContactPgRepository {
|
||||
DomainError::database_error(format!("Failed to get contacts by group: {}", e))
|
||||
})?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
contacts.push(Self::row_to_contact(row)?);
|
||||
}
|
||||
@@ -474,7 +474,7 @@ impl ContactRepository for ContactPgRepository {
|
||||
.await
|
||||
.map_err(|e| DomainError::database_error(format!("Failed to search contacts: {}", e)))?;
|
||||
|
||||
let mut contacts = Vec::new();
|
||||
let mut contacts = Vec::with_capacity(rows.len());
|
||||
for row in &rows {
|
||||
contacts.push(Self::row_to_contact(row)?);
|
||||
}
|
||||
|
||||
@@ -860,7 +860,12 @@ impl DedupService {
|
||||
|
||||
let mut received: Vec<(String, u64)> = Vec::new();
|
||||
let mut new_rows: Vec<(String, i64)> = Vec::new();
|
||||
let mut seen: HashSet<String> = HashSet::new();
|
||||
// Intra-request dedup set keyed on the raw 32-byte BLAKE3 digest
|
||||
// (`[u8; 32]`, `Copy` — no per-distinct-chunk 64-byte `String` heap
|
||||
// key), mirroring the streaming ingest loop (benches/ROUND17.md §D2).
|
||||
// hex ↔ digest is bijective, so membership is identical to the old
|
||||
// `HashSet<String>`.
|
||||
let mut seen: HashSet<[u8; 32]> = HashSet::new();
|
||||
|
||||
while let Some(frame) = frames.next().await {
|
||||
let data = frame?;
|
||||
@@ -870,14 +875,21 @@ impl DedupService {
|
||||
data.len()
|
||||
)));
|
||||
}
|
||||
let hash = blake3::hash(&data).to_hex().to_string();
|
||||
received.push((hash.clone(), data.len() as u64));
|
||||
if seen.insert(hash.clone()) {
|
||||
let len = data.len() as i64;
|
||||
let digest = blake3::hash(&data);
|
||||
let hash = digest.to_hex().to_string();
|
||||
let len = data.len();
|
||||
if seen.insert(*digest.as_bytes()) {
|
||||
self.backend
|
||||
.put_blob_from_bytes_unsynced(&hash, data)
|
||||
.await?;
|
||||
new_rows.push((hash, len));
|
||||
// First occurrence: `received` needs a copy, `new_rows` moves it.
|
||||
received.push((hash.clone(), len as u64));
|
||||
new_rows.push((hash, len as i64));
|
||||
} else {
|
||||
// Duplicate within this request — move the hex into `received`
|
||||
// (no clone; the blob is already registered by its first
|
||||
// occurrence). Same `received` sequence, input order preserved.
|
||||
received.push((hash, len as u64));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1216,24 +1228,30 @@ impl DedupService {
|
||||
return Ok(());
|
||||
}
|
||||
let mut guard = state.lock().await;
|
||||
let hashes: Vec<String> = batch.iter().map(|(h, _)| h.clone()).collect();
|
||||
|
||||
// Pin-or-classify in one statement: rows that exist take this
|
||||
// session's reference NOW; hashes not returned don't exist and are
|
||||
// ours to write.
|
||||
let pinned: HashSet<String> = sqlx::query_scalar::<_, String>(
|
||||
"UPDATE storage.blobs SET ref_count = ref_count + 1, orphaned_at = NULL
|
||||
WHERE hash = ANY($1)
|
||||
RETURNING hash",
|
||||
)
|
||||
.bind(&hashes)
|
||||
.fetch_all(pool.as_ref())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DomainError::internal_error("Dedup", format!("Failed to pin existing chunks: {e}"))
|
||||
})?
|
||||
.into_iter()
|
||||
.collect();
|
||||
// ours to write. Bind borrowed `&str`s — sqlx encodes `&[&str]` to
|
||||
// `text[]` identically to the owned Strings the old `.clone()` built,
|
||||
// so no per-chunk hash String is allocated just to run the query
|
||||
// (the pattern favorites_pg_repository.rs:271 already uses). The
|
||||
// borrow is scoped so it ends before `batch` is moved below.
|
||||
let pinned: HashSet<String> = {
|
||||
let hashes: Vec<&str> = batch.iter().map(|(h, _)| h.as_str()).collect();
|
||||
sqlx::query_scalar::<_, String>(
|
||||
"UPDATE storage.blobs SET ref_count = ref_count + 1, orphaned_at = NULL
|
||||
WHERE hash = ANY($1)
|
||||
RETURNING hash",
|
||||
)
|
||||
.bind(&hashes)
|
||||
.fetch_all(pool.as_ref())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
DomainError::internal_error("Dedup", format!("Failed to pin existing chunks: {e}"))
|
||||
})?
|
||||
.into_iter()
|
||||
.collect()
|
||||
};
|
||||
|
||||
let mut to_write: Vec<(String, Bytes)> = Vec::with_capacity(batch.len());
|
||||
for (hash, data) in batch {
|
||||
|
||||
Reference in New Issue
Block a user