perf(api): list proposals four at a time (closes #171) 46b5e2c59c6b
Listing proposals worked out each one's projected state and verdict one after another; on a bucket-backed repository each is many round trips to the store, and doomsdayone/metalgear's list took 28.4 s. Now four at a time, in order, each under the repository's read lock only.
- Author
- Andy McCall @andymccall <[email protected]>
- When
- · 2026-10-13 23:36 UTC
- Signature
- ✔ signed by Andy McCall
- Hash
46b5e2c59c6be7cf63ceafffe1e15a75044144a7bba40e6e48058a91ccccbc80- Depends on
- 0b20f1ec4007 d23464c0ac60 de505df5cf8d
- Depended on by
- d7e2c2754807 3571c519ada6
- In bookmarks
- api-unknown-fields cancel-landed-runs diffusion-pin-bookmark draft-ready main oh-comment-body-file proposal-list-fast
- In tags
- v0.5.0 v0.5.1 v0.5.2 v0.6.0 v0.6.1 v0.7.0 v0.8.0 v0.9.0 v0.9.1 v0.9.11 v0.9.15 v0.9.16 v0.9.19 v0.9.2 v0.9.20 v0.9.21 v0.9.22 v0.9.23 v0.9.24 v0.9.3 v0.9.4 v0.9.5 v0.9.6 v0.9.7 v0.9.8 v0.9.9
- Edits
- 2 atoms, 0 tree operations, 1 file
Browse the tree at this change
modified crates/hub/src/api/mod.rs +19 −7
| … | 709 unchanged lines |
||
| 710 | 710 | "all" => &["open", "draft", "landed", "closed"], |
|
| 711 | 711 | other => return Err(ApiError(StatusCode::BAD_REQUEST, format!("state is open, draft, landed, closed or all, not {other:?}"))), |
|
| 712 | 712 | }; |
|
| 713 | - | let mut out = Vec::new(); |
|
| 714 | - | for p in db::proposals::list(&app.db, row.id, states).await? { |
|
| 715 | - | let loaded = crate::web::proposals::load(&app, &repo, p).await?; |
|
| 716 | - | let v = crate::web::proposals::verdict(&app, &loaded, row.id).await?; |
|
| 717 | - | out.push(crate::web::proposals::to_api(&app, &owner, &name, &loaded, &v, None).await); |
|
| 718 | - | } |
|
| 719 | - | Ok(Json(out)) |
|
| 713 | + | // Each proposal's projected state is worked out from the store, which |
|
| 714 | + | // for a bucket-backed repository is many slow round trips: four at a |
|
| 715 | + | // time, in order, each under the repository's read lock only. One at |
|
| 716 | + | // a time took 28 s for a repository on GCS (oxygenhub#171). |
|
| 717 | + | use futures::StreamExt; |
|
| 718 | + | let rows = db::proposals::list(&app.db, row.id, states).await?; |
|
| 719 | + | let out: Vec<Result<Proposal, ApiError>> = futures::stream::iter(rows) |
|
| 720 | + | .map(|p| { |
|
| 721 | + | let (app, repo, owner, name, id) = (app.clone(), repo.clone(), owner.clone(), name.clone(), row.id); |
|
| 722 | + | async move { |
|
| 723 | + | let loaded = crate::web::proposals::load(&app, &repo, p).await?; |
|
| 724 | + | let v = crate::web::proposals::verdict(&app, &loaded, id).await?; |
|
| 725 | + | Ok(crate::web::proposals::to_api(&app, &owner, &name, &loaded, &v, None).await) |
|
| 726 | + | } |
|
| 727 | + | }) |
|
| 728 | + | .buffered(4) |
|
| 729 | + | .collect() |
|
| 730 | + | .await; |
|
| 731 | + | Ok(Json(out.into_iter().collect::<Result<Vec<_>, _>>()?)) |
|
| 720 | 732 | } |
|
| 721 | 733 | ||
| 722 | 734 | async fn proposal( |
|
| … | 986 unchanged lines |