perf: runtime performance pass, phase 1 (DB pragmas, batched scan writes)

Executes docs/superpowers/specs/2026-07-07-runtime-performance-pass-design.md:

- database.rs: every pooled connection now runs WAL + synchronous=NORMAL
  + busy_timeout=5000 + foreign_keys=ON via with_init (in-memory pools
  get the latter two). Fixes the SQLITE_BUSY failure mode under the
  parallel scanner and makes FK cascades work on every connection, not
  just whichever one last ran the old one-shot pragma in delete_folder.
- database.rs: prepare_cached for the per-video info_cache queries; new
  info_cache_put_many bulk upsert (single transaction) replaces the
  per-row autocommitted info_cache_put, with a round-trip unit test.
- library.rs: enrich_with_cache collects cache misses and flushes them
  once per channel; title sort uses sort_by_cached_key.
- app.rs: Title/ChannelAsc sorts use sort_by_cached_key instead of
  allocating two lowercased strings per comparison.
- Cargo.toml: panic=abort in release (crash-hook still fires; Cargo
  forces unwind for test harnesses). PACKAGING.md documents the local
  RUSTFLAGS target-cpu=native opt-in; repo stays portable.
- .gitignore: catacomb.db-wal / catacomb.db-shm sidecars.

Verified: 146 tests pass (integration suite exercises the pragmas
end-to-end), x86_64-pc-windows-gnu check green, live smoke shows
journal_mode=wal + batched cache writes landing correct rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-07-08 01:22:40 -07:00
parent 891b1e0d3c
commit d4bed019b2
No known key found for this signature in database
7 changed files with 299 additions and 34 deletions

View file

@ -938,7 +938,9 @@ impl App {
}
match self.sort_mode {
SortMode::Title => cards.sort_by(|a, b| a.title.to_lowercase().cmp(&b.title.to_lowercase())),
// sort_by_cached_key lowercases each element once instead of
// twice per comparison (O(n) allocations, not O(n log n)).
SortMode::Title => cards.sort_by_cached_key(|c| c.title.to_lowercase()),
SortMode::DurationAsc => cards.sort_by(|a, b| {
a.duration_secs.unwrap_or(0.0)
.partial_cmp(&b.duration_secs.unwrap_or(0.0))
@ -977,9 +979,8 @@ impl App {
cards.sort_by(|a, b| a.mtime_unix.unwrap_or(u64::MAX).cmp(&b.mtime_unix.unwrap_or(u64::MAX)));
}
SortMode::ChannelAsc => {
cards.sort_by(|a, b| {
a.channel_name.to_lowercase().cmp(&b.channel_name.to_lowercase())
.then_with(|| a.title.to_lowercase().cmp(&b.title.to_lowercase()))
cards.sort_by_cached_key(|c| {
(c.channel_name.to_lowercase(), c.title.to_lowercase())
});
}
}