OXYGENHUB

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