Skip to content

Performance observation: N+1 RPC pattern in request-client.js when hydrating request lists #1725

@Cripto5588

Description

@Cripto5588

Summary

While building a dashboard that fetches request lists via fromIdentity(), I noticed that each request requires an individual .refresh() call. Looking at the code paths, each refresh() appears to trigger multiple network calls (data fetch + payment balance + extensions).

This creates observable RPC fan-out when dashboards display 100+ requests. The issue is not a bug but a performance characteristic worth documenting – there doesn't seem to be a batch hydration API for consumers.

Relevant Code Paths (observed)

Location 1: packages/request-client.js/src/request-client.ts – method fromIdentity() / getRequestsByAddress()

From what I can trace:

  • Fetches request IDs for an identity
  • Loops through IDs and calls getRequestFromId() for each
  • Returns an array of Request objects that are not fully hydrated

Location 2: packages/request-client.js/src/request.ts – method refresh()

The refresh() method appears to:

  • Fetch fresh request data (getData())
  • Refresh payment network balance if present
  • Refresh extension states

Each of these likely translates to separate network/RPC calls (though I haven't measured the exact count per provider).

Stress Test Observations (40 minutes)

Setup used for testing:

  • Dashboard displaying 500 requests (simulated wallet history)
  • Polling refresh every 15 seconds (common pattern in accounting UIs)
  • Standard hydration: Promise.all(requests.map(r => r.refresh()))

What I measured during the test:

Metric Observed value
Total RPC calls (40 min) ~8,200
RPC calls per refresh cycle ~95-100
p95 latency per cycle ~3.8 seconds
Browser heap growth Started at ~35MB, reached ~280MB
UI freezes (>500ms) 23 occurrences

Test artifacts (attached):

  • network-tab-rpc-calls.png – parallel RPC requests visible in DevTools
  • performance-profile.png – long tasks during refresh cycles
  • memory-timeline.png – heap growth over time
  • console-logs-40min.txt – full test output with timestamps

These numbers come from my specific test environment (Infura RPC, Chrome browser, mock wallet with 500 requests). Actual numbers may vary by provider and request complexity.

What I'm seeing (not a formal root cause)

  • fromIdentity() returns requests that aren't fully hydrated
  • No obvious batch method like refreshMany() or getRequestsBatch() in the public API
  • Each refresh() triggers what looks like independent network calls
  • For dashboards with many requests, this adds up quickly

Example from documentation (simplified):

const requests = await requestNetwork.fromIdentity(identity);
await Promise.all(requests.map(r => r.refresh()));
This pattern works functionally but seems to generate linear RPC growth with list size.

Impact (observed in real usage)
Dashboards feel slower as request count grows past 100-200

RPC provider rate limits become a concern for production apps

Memory usage increases with frequent refresh cycles

Multiple dashboard tabs multiply the effect

Affected use cases (based on forum discussions):

Wallet transaction histories

Invoice/accounting dashboards

NFT payment explorers

Any UI showing lists of requests with real-time status

Question to maintainers
Is this expected behavior given the current architecture?

I'm trying to understand if:

There's internal batching I'm missing (e.g., Data Access layer aggregating calls)

This is a known trade-off with the current client design

A batch hydration API is something worth considering for future improvements

I'm not proposing solutions here – just documenting my observations and test results for discussion.

Attachments
request-client-fromIdentity-snippet.png (code path screenshot)

request-refresh-snippet.png (refresh method screenshot)

network-rpc-waterfall.png (DevTools showing parallel calls)

performance-flamegraph.png (performance recording)

memory-heap-timeline.png (memory growth)

test-logs-40min.txt (full console output)

Test environment
Request Network client version: latest from master (commit [insert hash if available])

RPC provider: Infura (mainnet)

Browser: Chrome 120

Test duration: 40 minutes

Request count in dashboard: 500 (simulated)

Happy to provide more details or run specific tests if helpful. Thanks for maintaining the project!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    🆕 New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions