MetaMask / MetaMask/metamask-extension
[P2] Cut redundant parallel API calls flagged as N+1: per-chain token fan-out and chunked calls to batch endpoints
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.2k
- Forks
- 5.6k
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 451
Description
**File:** `app/scripts/controllers/`, assets/price polling call sites (extension + `assets-controllers` in MetaMask/core)
**Size:** M | **Hours:** ~12–16 across families
---
## Problem
Sentry's span-based N+1 detection flags four families of redundant parallel API calls in production (client-side `http.client` spans, 0.5% trace sample, so real volumes are ≈130–200× the sampled counts below; 14d window as of 2026-07-22):
- **Per-chain token-list fan-out** — [METAMASK-YJQ6](https://metamask.sentry.io/issues/METAMASK-YJQ6) (23.5k sampled events / 21.7k users): ~9 parallel `GET token.api.cx.metamask.io/tokens/{chainId}` calls, ~3.5s each, on every service-worker cold start. Marked resolved in Sentry but still firing (last seen 2026-07-21) — needs reopen or explicit won't-fix.
- **Per-chain asset listings from the UI** — [METAMASK-YQNF](https://metamask.sentry.io/issues/METAMASK-YQNF): a per-chain loop against `tokens.api.cx.metamask.io/v3/chains/{chainId}/assets`, landing on `/sidepanel.html` boot. ([METAMASK-XS5Y](https://metamask.sentry.io/issues/METAMASK-XS5Y) was previously grouped here by list proximity; its offending span is an unrelated `*.ipfs.dweb.link` IPFS gateway call, not this endpoint — removed from this family, unclassified.)
- **Chunked calls to endpoints that already accept batches** — [METAMASK-YP3F](https://metamask.sentry.io/issues/METAMASK-YP3F), [METAMASK-YGPQ](https://metamask.sentry.io/issues/METAMASK-YGPQ): repeated parallel `v3/assets?assetIds=…` and `v3/spot-prices?assetIds=…` calls with different `assetIds` chunks. The batch parameter already exists; the client loops anyway.
- **Per-URL dapp scanning** — [METAMASK-XS1P](https://metamask.sentry.io/issues/METAMASK-XS1P): one `dapp-scanning.api.cx.metamask.io/v2/scan?url=…` call per URL.
Beyond the redundant load, the ~9-parallel shape exceeds Chrome's 6-connections-per-origin limit, so part of each observed 3.5s is browser connection-pool queueing ([METAMASK-YQS9](https://metamask.sentry.io/issues/METAMASK-YQS9), HTTP/1.1 overhead, is the same physics surfacing separately). The per-chain UI family also sits on the sidepanel first-paint critical path.
---
## Solution
Per family, cheapest first:
1. **`assetIds` chunking (YP3F, YGPQ):** consolidate call sites to use the existing batch parameters in a single request per poll cycle (plus request dedupe across concurrent callers). No backend changes required.
2. **Per-chain fan-out (YJQ6, YQNF):** collapse the per-chain loop into one batched fetch where the API supports it; where it does not, file the batch-endpoint ask with the owning team, quantified with the volumes above.
3. **Dapp scanning (XS1P):** cache scan results per origin and/or request a bulk-scan endpoint.
4. Reopen [METAMASK-YJQ6](https://metamask.sentry.io/issues/METAMASK-YJQ6) so regression tracking reflects reality.
### Would distributed-tracing backend spans help fix this category?
Related, not blocking, and worth being explicit about the boundary: **yes for attribution and pricing, no for detection or the fix itself.** Sentry's N+1 detector already runs on client `http.client` spans and works today, and every remediation above is client-side call-shape work — two of the four families call endpoints whose batch parameters already exist, so no backend visibility is needed to ship those fixes.
What backend `http.server` spans would add, if the four services were onboarded:
- **Server-time vs client-connection-queueing decomposition.** The client's ~3.5s per call bundles connection-pool wait, network, and server time — the ~9-parallel shape already exceeds Chrome's 6-connections-per-origin limit, so part of that 3.5s is guaranteed pool queueing regardless of server speed. A backend span splits it: server time ~100ms means the batching gains in the Solution section above are enormous; server-dominated time means a capacity/cache conversation with the API team is the higher-leverage fix.
- **Redundancy and cache-hit proof.** Server-side visibility that the N per-chain calls are near-identical requests (and whether they hit CDN cache) turns the per-chain batch-endpoint ask (Solution step 2) into a costed, evidenced request instead of a hunch.
- **Prioritization by real cost:** per-endpoint server cost × client fan-out volume, instead of ranking by sampled client event counts, which undercount reality by the sampling factor (~130–200×) and say nothing about server load.
Trace propagation itself is live in production today — ~32.7k `http.server` spans under `tenant.id:mmcx` joined client traces over a 7d window — but **none of the four services behind these N+1 families (token.api, tokens.api, price.api, dapp-scanning.api) emit trace-joined spans yet.** This is not an unstarted ask: [SRE-1761](https://consensyssoftware.atlassian.net/browse/SRE-1761) "MMCX backend trace instrumentation" (In Progress) already itemizes three of the four exact endpoints — `token.api.cx.metamask.io/tokens/{chainId}`, `tokens.api.cx.metamask.io/v3/assets`, and `price.api.cx.metamask.io/v3/spot-prices` — among the MMCX backends queued for instrumentation, one ticket per endpoint. Only `dapp-scanning.api.cx.metamask.io` is not yet on that list. Two caveats regardless of when it lands: at 0.5% head sampling only sampled traces carry backend halves, and until the service-worker trace root is bounded (MetaMask-planning#7354) these N+1s sit under its mega-trace root, which makes per-operation reading painful.
---
## Acceptance Criteria
- [ ] `assetIds`-family call sites issue one batched request per poll cycle; [METAMASK-YP3F](https://metamask.sentry.io/issues/METAMASK-YP3F) / [METAMASK-YGPQ](https://metamask.sentry.io/issues/METAMASK-YGPQ) event counts drop to ~0 on the fixed release.
- [ ] Per-chain token/asset fetches are batched or explicitly ticketed as a backend ask with volume evidence.
- [ ] [METAMASK-YJQ6](https://metamask.sentry.io/issues/METAMASK-YJQ6) reopened (or won't-fixed with rationale) and re-resolved only by a release where its event count actually drops.
- [ ] No new N+1 detections introduced by the consolidation (same Sentry detector, same surfaces).
---
## Labels
`team-assets`, `for-migration`
---
## Dependencies
**Related:** MetaMask-planning#7354 (service-worker trace-root bounding — these detections currently fire under the SW mega-trace root), MetaMask-planning#7238 (trace-context propagation program, backend-span onboarding path)
**Jira:** [ASSETS-3796](https://consensyssoftware.atlassian.net/browse/ASSETS-3796) (this ticket's content, ASSETS project); relates to [ASSETS-3698](https://consensyssoftware.atlassian.net/browse/ASSETS-3698) (pre-existing, covers the per-chain token fan-out family in more depth, currently Blocked — check its "deprecating `tokensController` fixes this" comment before scoping a batch endpoint for that family here)
[ASSETS-3796]: https://consensyssoftware.atlassian.net/browse/ASSETS-3796?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SRE-1761]: https://consensyssoftware.atlassian.net/browse/SRE-1761?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in app/scripts/controllers/ and the assets-controllers call sites, first tracing the assetIds requests for v3/assets and v3/spot-prices. Check whether each family supports one batched request per poll cycle and review ASSETS-3698 before scoping per-chain changes. Done means redundant call counts fall to approximately zero, remaining families are explicitly ticketed, and no new N+1 detections appear.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, performance
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100