perf(round25): in-place encrypted decrypt, dedup hash move, dead folder-Query, playlist N+1 fold, contact vcard over-fetch

Five benchmark-gated optimizations from a fresh six-way audit (benches/ROUND25.md),
each with a BEFORE/AFTER gate that rolls back if AFTER does not beat BEFORE:

- M1 EncryptedBlobBackend::decrypt_bytes: replace split_off (a fresh Vec + full
  ciphertext memcpy on every decrypted chunk, contradicting its own "in place"
  doc) with in-place detached decrypt + a zero-copy Bytes::slice past the nonce.
  Peak RAM per read drops from ~2x to ~1x the payload (-262KB/op at 256KiB;
  scales with blob size). Plaintext byte-identical; tamper/wrong-key tests pass.
- M2 delta commit: move-unzip the owned chunk list instead of a third
  per-occurrence hash clone (-4000 allocs on a 4000-chunk commit).
- M3 folder ZIP download: drop the dead Query<HashMap> extractor it never read
  (byte-identical response; 5->0 allocs/request).
- Q1 public-playlist listing: fold the per-playlist COUNT(*) N+1 into one
  LEFT JOIN ... GROUP BY via a new inherent repo method (101 -> 1 round-trips,
  36x wall on a 100-playlist page).
- Q2 contact REST listings (paginated/search/by-group): stop over-fetching the
  multi-KB vcard TEXT the ContactDto discards, via a shared lite row mapper and
  narrowed SELECTs (6.4x wall on 1000 contacts with 8KiB vcards). The whole-book
  vCard export and CardDAV sync paths keep the column.

Adds bench_round25_micro (counting allocator tracking count+bytes) and
bench_round25_queries (live Postgres), both with equivalence gates and a
rollback exit(1). Verified: cargo fmt clean, cargo clippy -D warnings clean,
cargo test --lib --features bench = 529 passed / 0 failed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8gs91AhmazoxMsDcNk3KT
This commit is contained in:
Claude
2026-07-21 00:07:44 +00:00
parent 0d82632f92
commit e8e4ef4b15
10 changed files with 1142 additions and 35 deletions
@@ -21,8 +21,28 @@ impl ContactPgRepository {
Self { pool }
}
/// Maps a database row to a Contact domain entity
/// Maps a database row to a Contact domain entity (reads the `vcard` column).
fn row_to_contact(row: &sqlx::postgres::PgRow) -> Result<Contact, DomainError> {
Self::row_to_contact_with_vcard(row, row.get("vcard"))
}
/// Maps a row whose SELECT omitted the `vcard` column — used by the REST
/// listings (paginated / search / by-group) whose `ContactDto` drops vcard
/// anyway, so the multi-KB vCard TEXT (which can embed a base64 PHOTO) is
/// never SELECTed, shipped over the wire, or allocated (benches/ROUND25.md
/// §Q2). The domain `Contact` keeps an empty vcard; these paths never
/// re-emit it. Do NOT use for CardDAV sync / whole-book export, which need
/// the round-trip vCard.
fn row_to_contact_lite(row: &sqlx::postgres::PgRow) -> Result<Contact, DomainError> {
Self::row_to_contact_with_vcard(row, String::new())
}
/// Shared row → `Contact` mapper; `vcard` is supplied by the caller so the
/// TEXT column can be omitted from listings that don't consume it.
fn row_to_contact_with_vcard(
row: &sqlx::postgres::PgRow,
vcard: String,
) -> Result<Contact, DomainError> {
// Decode each JSONB column straight into its typed Vec via
// `sqlx::types::Json<T>` (a single `serde_json::from_slice` pass over
// the raw JSONB bytes) instead of `row.get::<serde_json::Value>` +
@@ -63,7 +83,7 @@ impl ContactPgRepository {
row.get::<Option<String>, _>("photo_url"),
row.get("birthday"),
row.get("anniversary"),
row.get("vcard"),
vcard,
row.get("etag"),
row.get("created_at"),
row.get("updated_at"),
@@ -366,7 +386,7 @@ impl ContactRepository for ContactPgRepository {
SELECT
id, address_book_id, uid, full_name, first_name, last_name, nickname,
email, phone, address, organization, title, notes, photo_url,
birthday, anniversary, vcard, etag, created_at, updated_at
birthday, anniversary, etag, created_at, updated_at
FROM carddav.contacts
WHERE address_book_id = $1
ORDER BY full_name, first_name, last_name
@@ -387,7 +407,7 @@ impl ContactRepository for ContactPgRepository {
let mut contacts = Vec::with_capacity(rows.len());
for row in &rows {
contacts.push(Self::row_to_contact(row)?);
contacts.push(Self::row_to_contact_lite(row)?);
}
Ok(contacts)
}
@@ -429,7 +449,7 @@ impl ContactRepository for ContactPgRepository {
SELECT
c.id, c.address_book_id, c.uid, c.full_name, c.first_name, c.last_name, c.nickname,
c.email, c.phone, c.address, c.organization, c.title, c.notes, c.photo_url,
c.birthday, c.anniversary, c.vcard, c.etag, c.created_at, c.updated_at
c.birthday, c.anniversary, c.etag, c.created_at, c.updated_at
FROM carddav.contacts c
INNER JOIN carddav.group_memberships m ON c.id = m.contact_id
WHERE m.group_id = $1
@@ -445,7 +465,7 @@ impl ContactRepository for ContactPgRepository {
let mut contacts = Vec::with_capacity(rows.len());
for row in &rows {
contacts.push(Self::row_to_contact(row)?);
contacts.push(Self::row_to_contact_lite(row)?);
}
Ok(contacts)
}
@@ -462,9 +482,9 @@ impl ContactRepository for ContactPgRepository {
SELECT
id, address_book_id, uid, full_name, first_name, last_name, nickname,
email, phone, address, organization, title, notes, photo_url,
birthday, anniversary, vcard, etag, created_at, updated_at
birthday, anniversary, etag, created_at, updated_at
FROM carddav.contacts
WHERE address_book_id = $1
WHERE address_book_id = $1
AND (
full_name ILIKE $2
OR first_name ILIKE $2
@@ -485,7 +505,7 @@ impl ContactRepository for ContactPgRepository {
let mut contacts = Vec::with_capacity(rows.len());
for row in &rows {
contacts.push(Self::row_to_contact(row)?);
contacts.push(Self::row_to_contact_lite(row)?);
}
Ok(contacts)
}
@@ -22,6 +22,21 @@ struct PlaylistRow {
updated_at: DateTime<Utc>,
}
/// A public playlist row carrying its aggregated track count, produced by the
/// single `LEFT JOIN … GROUP BY` that replaces the per-playlist `COUNT(*)` N+1.
#[derive(FromRow)]
struct PublicPlaylistCountRow {
id: Uuid,
name: String,
description: Option<String>,
owner_id: Uuid,
is_public: bool,
cover_file_id: Option<Uuid>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
track_count: i64,
}
#[derive(FromRow)]
struct PlaylistItemRow {
id: Uuid,
@@ -79,6 +94,52 @@ impl PlaylistPgRepository {
pub fn pool(&self) -> &PgPool {
&self.pool
}
/// Public playlists together with their track counts in a **single**
/// round-trip. Replaces the adapter's 1 + N shape (one listing SELECT then
/// one `SELECT COUNT(*) FROM audio.playlist_items` per returned playlist —
/// up to 101 round-trips at `limit = 100`) with one `LEFT JOIN … GROUP BY`,
/// backed by `idx_playlist_items_playlist_id` (benches/ROUND25.md §Q1).
pub async fn list_public_playlists_with_counts(
&self,
limit: i64,
offset: i64,
) -> PlaylistRepositoryResult<Vec<(Playlist, i64)>> {
let rows = sqlx::query_as::<_, PublicPlaylistCountRow>(
"SELECT p.id, p.name, p.description, p.owner_id, p.is_public, p.cover_file_id, \
p.created_at, p.updated_at, COUNT(pi.id) AS track_count \
FROM audio.playlists p \
LEFT JOIN audio.playlist_items pi ON pi.playlist_id = p.id \
WHERE p.is_public = TRUE \
GROUP BY p.id \
ORDER BY p.updated_at DESC LIMIT $1 OFFSET $2",
)
.bind(limit)
.bind(offset)
.fetch_all(&*self.pool)
.await
.map_err(|e| {
DomainError::database_error(format!("Failed to list public playlists: {}", e))
})?;
rows.into_iter()
.map(|row| {
let track_count = row.track_count;
Playlist::with_id(
row.id,
row.name,
row.description,
row.owner_id,
row.is_public,
row.cover_file_id,
row.created_at,
row.updated_at,
)
.map(|p| (p, track_count))
.map_err(|e| DomainError::new(ErrorKind::InternalError, "Playlist", e.to_string()))
})
.collect()
}
}
impl PlaylistRepository for PlaylistPgRepository {