Repository navigation
perf(core-flows): index variants by id in cart line item preparation - #16233
Conversation
prepareVariantsAndItemsWithPricesStep mapped over the cart's line items and
scanned variantsData with .find() for each one. variantsData is fetched for the
variants those same items reference, so both sides grow together and the step
was O(items x variants).
The step runs from refresh-cart-items (every cart mutation), add-to-cart,
create-carts, create-order and add-line-items, so the cost is paid repeatedly
across a cart's lifecycle.
Index by id once and look up in O(1). First match wins, so the result is
identical to find.
Measured (both variants copied verbatim, output compared for equality first,
median of 7 runs, one distinct variant per line item):
100 items 0.152ms -> 0.028ms 5.5x
500 items 2.241ms -> 0.155ms 14.4x
1,000 items 7.135ms -> 0.208ms 34.3x
2,000 items 27.602ms -> 0.473ms 58.4x
For a typical B2C cart of 5-20 lines this is microseconds; it matters on large
carts (bulk/B2B, imported carts).
Closes medusajs#16232
🦋 Changeset detectedLatest commit: e3b01e9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 79 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Thanks for the contribution! A few items need to be addressed before this can move forward: Performance optimization that replaces a linear scan with a Map-based O(1) lookup in prepareVariantsAndItemsWithPricesStep. The logic is correct and behavior-preserving. One required change: the changeset message uses an unsupported perf(...) prefix. No tests were added; for a trivial, behavior-identical optimization this is borderline acceptable, but the author has offered to add them if requested.
Triggered by: manual workflow dispatch |
The changeset bot only accepts fix/feat/chore; perf(...) was rejected.
|
Thanks for the contribution! Initial automated review looks good. Performance optimization replacing a linear find scan with a Map-based O(1) lookup in prepareVariantsAndItemsWithPricesStep. The previously required fix (changeset message format) has been addressed — the message now correctly uses the chore(core-flows): prefix. The code change is behavior-identical (first-match-wins semantics preserved via the !variantsById.has guard), no security issues, no bugs, and no regressions introduced. Triggered by: new commit pushed |
Summary
What —
prepareVariantsAndItemsWithPricesStepmaps over the cart's line items and scansvariantsDatawith.find()for every one of them:variantsDatais fetched for the variants those same items reference, so both sides grow together — the step isO(items × variants).Why — this step is on the cart's hot path, not a one-off. It runs from:
cart/workflows/refresh-cart-items.tscart/workflows/add-to-cart.tscart/workflows/create-carts.tsorder/workflows/create-order.tsorder/workflows/add-line-items.tsHow — index the variants by
idonce, then look up in O(1):First match wins, so the result is identical to
find. This is the same indexing pattern already used elsewhere in the cart flows for id matching.Benchmark
Both variants copied verbatim from this file, output compared for equality before timing, median of 7 runs, one distinct variant per line item (the normal case):
The shape is what matters: doubling the line items roughly quadruples the current cost (7.1 ms → 27.6 ms from 1,000 to 2,000) while the indexed version stays linear.
Being straightforward about the scale: on a typical B2C cart of 5–20 lines this is microseconds and nobody will notice. It becomes real on large carts — bulk/B2B orders, imported carts, quote-style flows — where a single refresh can spend tens of milliseconds scanning. The change costs nothing at small sizes, so it is a floor-raiser rather than a fix for a reported incident.
Testing
Behaviour is unchanged:
Map.getreplaces afindover the same array with the same key, and the helper keeps first-occurrence-wins.Honest about what I could and couldn't run locally:
variantsDataisany[]here, so theMap<string, any>and.get(item.variant_id!)introduce no new narrowing. I checked the changed file in isolation and it produces no type errors beyond the module-resolution ones that the same file already produces without my change.Added a changeset (
@medusajs/core-flowspatch).Closes #16232