Skip to content

MWPW-200568 Parallelize initService script loading in merch block#6296

Open
TsayAdobe wants to merge 7 commits into
stagefrom
MWPW-200568
Open

MWPW-200568 Parallelize initService script loading in merch block#6296
TsayAdobe wants to merge 7 commits into
stagefrom
MWPW-200568

Conversation

@TsayAdobe

@TsayAdobe TsayAdobe commented Jul 14, 2026

Copy link
Copy Markdown
Contributor
  • initService in merch.js loaded four independent resources sequentially, creating an unnecessary waterfall on every page that uses the merch block.
  • Replaced sequential await chain with Promise.all([loadMasComponent, fragmentClient, getLocaleSettings, getValidatedMarket])

Resolves: MWPW-200568

Test URLs:

Change on https://www.adobe.com/fr/creativecloud.html with content overrides
Before:
BeforeBR

After
AfterPR

@TsayAdobe
TsayAdobe requested a review from a team as a code owner July 14, 2026 20:17
@aem-code-sync

aem-code-sync Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Hello, I'm the AEM Code Sync Bot and I will run some actions to deploy your branch.
In case there are problems, just click the checkbox below to rerun the respective action.

  • Re-sync branch
Commits

@vhargrave

vhargrave commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@TsayAdobe thank you very much for this contribution 🚀
Is there nothing else that can be done to speed up M@S ? This seems like it will only help with like 200-300 milliseconds whereas the performance hit is around 2 seconds when it loads in.
This is a major hit for lingo right now and if I'm interpreting this right and we're only getting minor savings then there will be more discussions on if we can roll out M@S for the English pages as well as part of the major August launch
cc: @Blainegunn @sukamat

@TsayAdobe

Copy link
Copy Markdown
Contributor Author

@vhargrave I also converted mas-mep-utils.js from a static to a dynamic import -- it was blocking merch.js from executing despite only being needed in MEP preview environments. Further gains beyond this would require architectural changes (e.g., server-side prerender to emit preload hints and collapse the discovery waterfall).

@Axelcureno Axelcureno left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice win on the waterfall — the loadScript swap and the mep-utils dynamic import both look right.

One thing to fix before merge: with geo detection on, getLocaleSettings and getValidatedMarket aren't actually independent. The sequential await was priming the akamai cache for the second call. In Promise.all they both hit geo2.adobe.com — I reproduced it locally, 1 fetch on stage vs 2 on this branch. Details inline.

On tests

There are none here, and the existing ones can't catch this. Every geo test either pre-seeds sessionStorage.akamai (merch.test.js:285, market.test.js:172/217/235/273) or stubs fetch to reject — so getCountry short-circuits at utils.js:952 and never hits the network. The suite only exercises the cache-hit path, which is exactly what the sequential await guaranteed and this PR removes.

Something like this fails on the branch, passes on stage:

it('does not double-fetch geo when geo detection is on', async () => {
  sessionStorage.removeItem('akamai');  // force the network path
  const fetchStub = sinon.stub(window, 'fetch');
  fetchStub.withArgs(sinon.match(/geo2\.adobe\.com/)).resolves({
    ok: true, json: () => Promise.resolve({ country: 'DE' }),
  });
  // set mas-geo-detection meta to 'on'
  await initService(true);
  expect(fetchStub.withArgs(sinon.match(/geo2\.adobe\.com/)).callCount).to.equal(1);
});

Also worth a countryFromMarket assertion with the cache cold, and a fragment-client test that initService still resolves when loadScript rejects — that .catch() is load-bearing now (without it one failed script rejects the whole Promise.all) and has no coverage today.

log?.error('Failed to load fragment-client.js:', e);
}
}
const [, , { language, locale, country }, validatedMarket] = await Promise.all([

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two look independent but aren't. Old order:

await getLocaleSettings()  → getCountry() → getAkamaiCode() → writes sessionStorage.akamai
await getValidatedMarket() → getCountry() → reads it ✅

The sequential await was doing load-bearing work — priming the cache before the second read. In parallel, both call getCountry() before either has written it. getAkamaiCode (geo.js:7) has no in-flight dedup and is cache: 'no-cache', so nothing collapses them.

I ran this locally against the branch — sequential gives 1 geo2.adobe.com fetch, parallel gives 2. So: two concurrent geo fetches per geo-enabled page load. Related risk I did not reproduce, but the code path is there — getValidatedMarket() resolving on a null country falls back to currLang.defaultMarket || 'us' (market.js:38), which would be a wrong-country price render on a slow or failing geo call.

Keeps most of the win — chain just the market call off the settings that prime the cache:

const localeSettingsPromise = getLocaleSettings(miloLocale);
const [, , { language, locale, country }, validatedMarket] = await Promise.all([
  loadMasComponent(COMMERCE_LIBRARY),
  fragmentClientUrl ? loadScript(fragmentClientUrl, 'module').catch(...) : Promise.resolve(),
  localeSettingsPromise,
  useGeoMarket
    ? localeSettingsPromise.then(() => import('../../utils/market.js')
        .then(({ getValidatedMarket }) => getValidatedMarket()))
    : Promise.resolve(null),
]);

loadMasComponent / loadScript / getLocaleSettings are genuinely independent, so that's where the real gain was anyway. Alternative: memoize the in-flight promise in getAkamaiCode — fixes the double-fetch for every caller, since getCountry and resolveDetectedMarketCountry both funnel through it.

@Axelcureno

Axelcureno commented Jul 16, 2026

Copy link
Copy Markdown
Member

@vhargrave you're right that this PR alone won't move the needle much — so we dug into where the ~2s actually goes. We traced the full load path and measured plans.html live. TL;DR: the data fetches are fast (40–140ms each); the time is serialized orchestration. On the measured page, the first merch data request can't fire until ~2.9s, then takes 60ms.

Where the time goes (measured, warm cache):

  • 0→1.4s: Milo bootstrap incl. personalization gate (checkForPageMods blocks all sections)
  • 1.4→2.9s: sequential section loop + block→service→bundle→element chain — commerce service is ready at 1.9s, but the first fragment fetch waits until 2.9s on section progression alone
  • 2.9→4.3s: fragment fetch → per-OSI price fetch, strictly serial (and price batching was lost in the Milo→MAS migration — one request per price element today)

What MAS team is fixing (tickets filed):

  • Restore WCS price batching (MWPW-200883)
  • Bundle refactor — commerce engine currently ships twice, all 24 variants always ship; ~148KB → ~80KB gz (MWPW-200884)
  • HTTP caching for fragment/price fetches (MWPW-200885)
  • Fragment prefetch API (MWPW-200886)
  • Detach card readiness from section flow in merch-card-autoblock (ours) — today a slow card holds every later section at display:none for up to 5s; after the fix, sections below merch reveal ~1–2.5s earlier. Would appreciate Milo review since it touches the reveal model.

Two small asks of Milo core (block-level code runs too late for both):

  1. Early preconnect/modulepreload for MAS origins at loadArea time when the page has merch links — or bless the metadata-preload pattern plans.html already uses by hand.
  2. A post-MEP, pre-section hook so we can prefetch fragment data (with correct MEP-overridden IDs) at ~1.4s instead of ~2.9s. This is the single biggest lever we found (~1.3–1.5s).

Projection: MAS fixes alone get cards to ~3s; with the two hooks, ~1.8–2.2s — within ~0.5s of the personalization floor, which is the practical best for any block on a personalized page. Longer-term ideas (parallel section loading with ordered reveal, scoped personalization) would help every block, not just merch — happy to share the full RCA and measurements if there's interest.

cc @Blainegunn @sukamat @TsayAdobe @reil-adobe @3ch023 @npeltier

@github-actions

Copy link
Copy Markdown
Contributor

This pull request is not passing all required checks. Please see this discussion for information on how to get all checks passing. Inconsistent checks can be manually retried. If a test absolutely can not pass for a good reason, please add a comment with an explanation to the PR.

@TsayAdobe

Copy link
Copy Markdown
Contributor Author

I added static hints for merch.js and merch-card-autoblock.js to preload MAS libs.

https://www.adobe.com/fr/creativecloud.html
Before
creativecloud_before
After
creativecloud_after

https://www.adobe.com/fr/products/photosho.html
Before
photoshop_before
After
photoshop_after

cc @Blainegunn @sukamat @vhargrave @Axelcureno @reil-adobe @3ch023 @npeltier

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants