Perf: batch bundle lookup; stop gating git info/refs on the clone budget
- item page: replace the per-id get_item_by_id loop over an unlisted item's
containing bundles with a single get_public_items_by_ids (ANY($1)) query.
- git: don't acquire git_smart_http_semaphore for info/refs. The ref
advertisement is a short bounded child (KB-sized, exits immediately) and is
GovernorLayer-rate-limited; sharing the upload-pack budget let a burst of
slow clones stall every new clone at the handshake. The cap now covers the
expensive streaming upload-pack only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 files changed,
+28 insertions,
-19 deletions
| 115 |
115 |
|
Ok(item)
|
| 116 |
116 |
|
}
|
| 117 |
117 |
|
|
|
118 |
+ |
/// Fetch multiple public items by id in one query. Replaces a per-id
|
|
119 |
+ |
/// `get_item_by_id` loop (N+1) where the caller only wants the public rows.
|
|
120 |
+ |
/// Order is not guaranteed.
|
|
121 |
+ |
#[tracing::instrument(skip_all)]
|
|
122 |
+ |
pub async fn get_public_items_by_ids(pool: &PgPool, ids: &[ItemId]) -> Result<Vec<DbItem>> {
|
|
123 |
+ |
if ids.is_empty() {
|
|
124 |
+ |
return Ok(Vec::new());
|
|
125 |
+ |
}
|
|
126 |
+ |
let items = sqlx::query_as::<_, DbItem>(
|
|
127 |
+ |
"SELECT * FROM items WHERE id = ANY($1) AND is_public = true",
|
|
128 |
+ |
)
|
|
129 |
+ |
.bind(ids)
|
|
130 |
+ |
.fetch_all(pool)
|
|
131 |
+ |
.await?;
|
|
132 |
+ |
|
|
133 |
+ |
Ok(items)
|
|
134 |
+ |
}
|
|
135 |
+ |
|
| 118 |
136 |
|
/// Fetch titles for a batch of item IDs. Returns (item_id, title) pairs.
|
| 119 |
137 |
|
#[tracing::instrument(skip_all)]
|
| 120 |
138 |
|
pub async fn get_item_titles_batch(pool: &PgPool, ids: &[ItemId]) -> Result<Vec<(ItemId, String)>> {
|
| 110 |
110 |
|
let root = repos_root(&state)?;
|
| 111 |
111 |
|
let repo_path = git::repo_disk_path(&root, &owner, repo_name)?;
|
| 112 |
112 |
|
|
| 113 |
|
- |
// Same per-clone permit as upload-pack; the ref advertisement is a `git`
|
| 114 |
|
- |
// child too, so it shares the fan-out budget.
|
| 115 |
|
- |
let _permit = state
|
| 116 |
|
- |
.git_smart_http_semaphore
|
| 117 |
|
- |
.acquire()
|
| 118 |
|
- |
.await
|
| 119 |
|
- |
.context("acquire git smart-http permit")?;
|
| 120 |
|
- |
|
|
113 |
+ |
// The ref advertisement is NOT gated by `git_smart_http_semaphore`: it's a
|
|
114 |
+ |
// short, bounded `git upload-pack --advertise-refs` that buffers only the
|
|
115 |
+ |
// (KB-sized) ref list and exits immediately, and the route is already
|
|
116 |
+ |
// rate-limited by the GovernorLayer. Sharing the upload-pack budget made a
|
|
117 |
+ |
// burst of slow clones (holding their permits during the pack transfer)
|
|
118 |
+ |
// stall every *new* clone at the handshake — the budget is for the
|
|
119 |
+ |
// expensive streaming half only (Run #21 Performance).
|
| 121 |
120 |
|
let output = tokio::process::Command::new("git")
|
| 122 |
121 |
|
.arg("upload-pack")
|
| 123 |
122 |
|
.arg("--stateless-rpc")
|
| 113 |
113 |
|
} else {
|
| 114 |
114 |
|
vec![]
|
| 115 |
115 |
|
};
|
| 116 |
|
- |
let containing_bundles: Vec<db::DbItem> = {
|
| 117 |
|
- |
let mut bundles = Vec::new();
|
| 118 |
|
- |
for bid in &containing_bundle_ids {
|
| 119 |
|
- |
if let Some(b) = db::items::get_item_by_id(&state.db, *bid).await?
|
| 120 |
|
- |
&& b.is_public
|
| 121 |
|
- |
{
|
| 122 |
|
- |
bundles.push(b);
|
| 123 |
|
- |
}
|
| 124 |
|
- |
}
|
| 125 |
|
- |
bundles
|
| 126 |
|
- |
};
|
|
116 |
+ |
// Single batched query instead of one get_item_by_id per bundle id (N+1).
|
|
117 |
+ |
let containing_bundles: Vec<db::DbItem> =
|
|
118 |
+ |
db::items::get_public_items_by_ids(&state.db, &containing_bundle_ids).await?;
|
| 127 |
119 |
|
|
| 128 |
120 |
|
// For bundle-type items, load the child items
|
| 129 |
121 |
|
let bundle_child_items = if db_item.item_type == ItemType::Bundle {
|