You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(recommendations): bound GET /api/recommendations with pagination and slimmer rows #777
PR for #723 gzips large JSON responses on Lambda, which raises the 6 MB ceiling several-fold but does not bound GET /api/recommendations. Row count still grows with the tenant.
Current behaviour
getRecommendations (internal/api/handler_recommendations.go) returns the full filtered cache; the store read (store_postgres_recommendations.go) has no ORDER BY, LIMIT or cursor. Scoping showed details is only about a quarter of a row (roughly 125 B of 500-600 B), so dropping it alone saves little.
Constraints found while scoping
frontend/src/dashboard.ts and frontend/src/recommendations.ts compute totals, grouping and sorting client-side over the full set; server pagination would need server-side summaries and a frontend change.
samePurchaseVariantIdentity (frontend/src/recommendations.ts) reads details fields from list rows to group priced variants; dropping details needs top-level discriminators or an on-demand fetch.
GET /api/recommendations/{id}/detail does not return the details blob.
Proposed fix
Keyset pagination with server-side summary, slimmer list rows, and documenting min_savings_* in openapi.yaml. Needs a design pass with the frontend owners.
References
Parent #723. Found by the 2026-10-08 review fleet (COR-04).
Scoping update. Nothing is coded yet: the issue asks for a design pass with the frontend owners, and the remaining choices are product decisions for the acting owner, not code.
The frontend (frontend/src/api/recommendations.tsgetRecommendations, called from dashboard.ts and recommendations.ts) never sends limit. It still takes the unbounded path, so a large tenant can still hit the 6 MB Lambda cap when the client doesn't accept gzip.
samePurchaseVariantIdentity (recommendations.ts ~1090) still reads details from list rows, so details can't be dropped from list rows yet.
Options
Frontend follows the cursor (small).getRecommendations sends limit=1000 and follows next_cursor until it is absent, concatenating the rows. Client-side grouping, sorting and totals stay as they are, and no response can go over the cap. Cost: N rows take ceil(N/1000) requests, and each one re-reads the whole filtered set on the server (O(P*N) today, still O(N) per page after perf(api): page recommendations from an ID-ordered keyset scan instead of the full list #1240), so a large tenant's load gets slower even though it no longer fails. Client memory is unchanged.
True server-side paging (large). The server sorts, groups and sums, and the frontend renders one page at a time, using summary for the totals. This bounds both the response and client memory. It needs server-side equivalents of the dashboard and recommendations grouping/sorting, and a redesign of the recommendations table interaction (sorting and grouping across pages, bulk select across pages).
Slimmer rows (medium, independent). Promote the fields samePurchaseVariantIdentity reads from details to top-level row fields (as already done for availability_zone), drop details from list rows, and serve it from GET /api/recommendations/{id}/detail. On its own this saves about a quarter of each row.
Recommendation: do option 1 now as the fix for the 502, since it is low risk and needs no design. Unpark #1240 once migration numbers settle, so each page stops re-sorting the whole set. Keep options 2 and 3 open until the recommendations UI rework is planned, because they mostly depend on how that table should behave.
Released: needs an owner decision between the options above; no code written
Activity
fix(frontend): balance summary grids, lift contrast, keep table focus (…
fix(api): add opt-in in-memory cursor pagination to GET /api/recommen…
IAmJSD commentedon Oct 26, 2026
Claiming: working on this
IAmJSD commentedon Oct 26, 2026
Scoping update. Nothing is coded yet: the issue asks for a design pass with the frontend owners, and the remaining choices are product decisions for the acting owner, not code.
Where things stand on
mainlimit/cursorpaging toGET /api/recommendations. It pages in memory, caps each page at 4 MiB, and still computessummary/regionsover the full filtered set. A request with neither param gets the old unbounded body.frontend/src/api/recommendations.tsgetRecommendations, called fromdashboard.tsandrecommendations.ts) never sendslimit. It still takes the unbounded path, so a large tenant can still hit the 6 MB Lambda cap when the client doesn't accept gzip.samePurchaseVariantIdentity(recommendations.ts~1090) still readsdetailsfrom list rows, sodetailscan't be dropped from list rows yet.Options
getRecommendationssendslimit=1000and followsnext_cursoruntil it is absent, concatenating the rows. Client-side grouping, sorting and totals stay as they are, and no response can go over the cap. Cost: N rows take ceil(N/1000) requests, and each one re-reads the whole filtered set on the server (O(P*N) today, still O(N) per page after perf(api): page recommendations from an ID-ordered keyset scan instead of the full list #1240), so a large tenant's load gets slower even though it no longer fails. Client memory is unchanged.summaryfor the totals. This bounds both the response and client memory. It needs server-side equivalents of the dashboard and recommendations grouping/sorting, and a redesign of the recommendations table interaction (sorting and grouping across pages, bulk select across pages).samePurchaseVariantIdentityreads fromdetailsto top-level row fields (as already done foravailability_zone), dropdetailsfrom list rows, and serve it fromGET /api/recommendations/{id}/detail. On its own this saves about a quarter of each row.Recommendation: do option 1 now as the fix for the 502, since it is low risk and needs no design. Unpark #1240 once migration numbers settle, so each page stops re-sorting the whole set. Keep options 2 and 3 open until the recommendations UI rework is planned, because they mostly depend on how that table should behave.
Released: needs an owner decision between the options above; no code written
IAmJSD commentedon Oct 26, 2026
Released: needs an owner decision between the options in the comment above; no code written
perf(api): page recommendations from an ID-ordered keyset scan instea…