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

Open
#44,750 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

for-migration INVALID-ISSUE-TEMPLATE team-assets
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.