Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

perf(store-dir): batch the prefetch SELECT to remove cold-cache regression#298

Merged
zkochan merged 3 commits into
mainfrom
fix-294
Apr 26, 2026
Merged

perf(store-dir): batch the prefetch SELECT to remove cold-cache regression#298
zkochan merged 3 commits into
mainfrom
fix-294

Conversation

@zkochan

@zkochan zkochan commented Apr 25, 2026

Copy link
Copy Markdown
Member

Summary

Closes #294.

Replaces the per-key SELECT loop in prefetch_cas_paths with a single batched SELECT key, data FROM package_index WHERE key IN (?, ?, …), chunked at 999 placeholders. SQLite walks the package_index PK B-tree once per chunk instead of once per key, collapsing N round-trips across the SQLite mutex into one query.

This drops the ~50 ms of all-miss query overhead the empty-store install paid after #292 introduced the install-wide prefetch — the cold-cache regression the issue reports.

Changes

  • StoreIndex::get_many(&[String]) -> Result<HashMap<String, PackageFilesIndex>, StoreIndexError> — batched lookup, chunked at GET_MANY_CHUNK = 999 (safe vs. legacy SQLITE_MAX_VARIABLE_NUMBER builds; rusqlite's bundled SQLite caps at 32766).
  • prefetch_cas_paths Phase 1 now calls guard.get_many(&cache_keys) instead of looping guard.get(&cache_key) per key.

Behaviour notes

  • Decode failures on a row are logged at debug! and skipped, matching load_cached_cas_paths's .ok()? stance on the per-key path — a malformed row is a cache miss, not a batch failure.
  • A SQLite error during the batched read bubbles up as StoreIndexError::Read; prefetch_cas_paths already turns that into a soft fallback (per-snapshot lookups).

Test plan

  • just ready clean (220 tests pass, 2 skipped; lint clean).
  • New unit tests in crates/store-dir/src/store_index.rs:
    • empty input
    • all-miss
    • all-hit
    • mixed hit / miss
    • undecodable-row skip (silent, matches per-key .ok()?)
    • more keys than GET_MANY_CHUNK to exercise chunking
  • Re-run cold-cache integrated-benchmark on alot7 Linux to confirm the regression closes (target ≤ ~2.85 s, was ~2.95 s on HEAD vs ~2.81 s on main).
  • Re-run warm-cache benchmark to confirm no regression vs perf: batch store-index prefetch + drive warm link phase on rayon #292.

…ssion

Replaces the per-key SELECT loop in `prefetch_cas_paths` with a single
batched `SELECT key, data FROM package_index WHERE key IN (?, ?, …)`
chunked at 999 placeholders. SQLite walks the PK B-tree once per chunk
instead of once per key, collapsing N round-trips across the SQLite
mutex into one and dropping the ~50 ms of all-miss query overhead the
empty-store install paid after #292.

Closes #294.
@codecov

codecov Bot commented Apr 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.36842% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.94%. Comparing base (5acbeed) to head (4bbb479).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
crates/tarball/src/lib.rs 50.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #298      +/-   ##
==========================================
+ Coverage   88.76%   88.94%   +0.17%     
==========================================
  Files          61       61              
  Lines        5698     5806     +108     
==========================================
+ Hits         5058     5164     +106     
- Misses        640      642       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Apr 25, 2026

Copy link
Copy Markdown

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00     15.6±0.52ms   277.5 KB/sec    1.00     15.6±0.24ms   277.3 KB/sec

@github-actions

github-actions Bot commented Apr 25, 2026

Copy link
Copy Markdown

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.511 ± 0.815 2.994 5.662 1.11 ± 0.28
pacquet@main 3.173 ± 0.301 2.846 3.731 1.00
pnpm 5.749 ± 0.281 5.459 6.227 1.81 ± 0.19
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.5108760668000003,
      "stddev": 0.8147367971723012,
      "median": 3.1775408147,
      "user": 1.9872251199999997,
      "system": 2.70889966,
      "min": 2.9938078621999997,
      "max": 5.6620812162,
      "times": [
        3.3057781872,
        3.0174286572,
        3.1228755212,
        3.1295406002,
        3.8776073132,
        3.7413318481999998,
        5.6620812162,
        3.0327684332,
        3.2255410292,
        2.9938078621999997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.1728979311999996,
      "stddev": 0.30058582109790166,
      "median": 3.0448236322,
      "user": 2.0012098199999997,
      "system": 2.6668003600000008,
      "min": 2.8463012892,
      "max": 3.7313833652,
      "times": [
        2.9952961652,
        3.6766630542,
        2.9888498971999997,
        2.8463012892,
        3.1489100592,
        3.0670753522,
        2.9906450502,
        3.2612831671999998,
        3.0225719122,
        3.7313833652
      ]
    },
    {
      "command": "pnpm",
      "mean": 5.749281026599999,
      "stddev": 0.2806855154925967,
      "median": 5.6476566922,
      "user": 6.686728219999999,
      "system": 3.31096196,
      "min": 5.4585686042,
      "max": 6.2274436832,
      "times": [
        5.9138554492,
        5.5170335462,
        5.4719221512,
        5.6894636752,
        6.0882047172,
        6.2274436832,
        5.9823768112,
        5.6058497092,
        5.4585686042,
        5.5380919192
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.039 ± 0.161 0.926 1.453 1.00
pacquet@main 1.170 ± 0.287 0.947 1.849 1.13 ± 0.33
pnpm 2.936 ± 0.626 2.262 3.891 2.83 ± 0.74
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.0386760451400001,
      "stddev": 0.16057293996373895,
      "median": 0.97284898474,
      "user": 0.20299773999999998,
      "system": 1.23595898,
      "min": 0.92613907774,
      "max": 1.45293447674,
      "times": [
        1.09305889674,
        0.9921977107400001,
        1.13358053374,
        0.9418974517400001,
        0.9790742317400001,
        1.45293447674,
        0.95502006374,
        0.92613907774,
        0.96662373774,
        0.94623427074
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.16996249704,
      "stddev": 0.2865548191899628,
      "median": 1.0595974207399999,
      "user": 0.20226704,
      "system": 1.2412922800000001,
      "min": 0.9467806857400001,
      "max": 1.84874050274,
      "times": [
        1.03727110674,
        1.16499519974,
        1.4608752817400001,
        0.99262435774,
        0.97283908174,
        0.95995748274,
        1.23361753674,
        1.84874050274,
        1.08192373474,
        0.9467806857400001
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.93641609054,
      "stddev": 0.6255359854453874,
      "median": 2.77788871624,
      "user": 2.21510464,
      "system": 1.6282113799999998,
      "min": 2.2618145227400004,
      "max": 3.89070011574,
      "times": [
        2.77249293274,
        3.65401254674,
        3.89070011574,
        2.47868372974,
        2.32433542874,
        2.2618145227400004,
        2.2713350127400003,
        3.4005507457400004,
        3.5269513707400004,
        2.78328449974
      ]
    }
  ]
}

@zkochan zkochan marked this pull request as ready for review April 25, 2026 22:15
Copilot AI review requested due to automatic review settings April 25, 2026 22:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves cold-cache install performance by batching store-index prefetch reads into chunked IN (...) queries to avoid per-key SQLite round-trips and mutex contention.

Changes:

  • Added StoreIndex::get_many to fetch many index rows via chunked SELECT key, data ... WHERE key IN (...).
  • Updated prefetch_cas_paths to use get_many instead of looping get per key.
  • Added unit tests for get_many covering empty/miss/hit/mixed/undecodable/chunking scenarios.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
crates/tarball/src/lib.rs Switches prefetch Phase 1 from per-key reads to a batched get_many call with fallback on error.
crates/store-dir/src/store_index.rs Implements StoreIndex::get_many with chunking and adds targeted unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/tarball/src/lib.rs
Comment thread crates/store-dir/src/store_index.rs Outdated
- prefetch_cas_paths: drop the `into_iter().collect()` that ran under
  the SQLite mutex; return the `HashMap` directly from the guard scope
  and let Phase 2 iterate it. Saves an O(n) copy inside the lock.
- Test doc: clarify that `get_many` skips undecodable rows but emits a
  `debug!` log for them — earlier wording said "silently".

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@zkochan zkochan requested review from KSXGitHub and anonrig April 25, 2026 23:07
Comment thread crates/store-dir/src/store_index.rs
KSXGitHub flagged the `format!("… IN ({placeholders})")` in #298 as
visually scary. It's safe — only the `?,?,…?` placeholder string is
interpolated, key values flow through `params_from_iter` — but a
comment makes the invariant explicit so future readers don't have to
re-derive it.
@zkochan zkochan merged commit b4a69e1 into main Apr 26, 2026
13 checks passed
@zkochan zkochan deleted the fix-294 branch April 26, 2026 08:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(store-dir): batch the prefetch SELECT to remove cold-cache regression from #292

4 participants