Skip to content

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

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:

MetricObserved value
Total RPC calls (40 min)~8,200
RPC calls per refresh cycle~95-100
p95 latency per cycle~3.8 seconds
Browser heap growthStarted 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):

constrequests=awaitrequestNetwork.fromIdentity(identity);awaitPromise.all(requests.map(r=>r.refresh()));ThispatternworksfunctionallybutseemstogeneratelinearRPCgrowthwithlistsize.Impact(observedinrealusage)Dashboardsfeelslowerasrequestcountgrowspast100-200RPCproviderratelimitsbecomeaconcernforproductionappsMemoryusageincreaseswithfrequentrefreshcyclesMultipledashboardtabsmultiplytheeffectAffectedusecases(basedonforumdiscussions):
WallettransactionhistoriesInvoice/accountingdashboardsNFTpaymentexplorersAnyUIshowinglistsofrequestswithreal-timestatusQuestiontomaintainersIsthisexpectedbehaviorgiventhecurrentarchitecture?
I'm trying to understand if:There'sinternalbatchingI'm missing (e.g., Data Access layer aggregating calls)
Thisisaknowntrade-offwiththecurrentclientdesignAbatchhydrationAPIissomethingworthconsideringforfutureimprovementsI'm not proposing solutions here – just documenting my observations and test results for discussion.
Attachmentsrequest-client-fromIdentity-snippet.png(codepathscreenshot)request-refresh-snippet.png(refreshmethodscreenshot)network-rpc-waterfall.png(DevToolsshowingparallelcalls)performance-flamegraph.png(performancerecording)memory-heap-timeline.png(memorygrowth)test-logs-40min.txt(fullconsoleoutput)TestenvironmentRequestNetworkclientversion: latestfrommaster(commit[inserthashifavailable])
RPC provider: Infura(mainnet)
Browser: Chrome120
Test duration: 40minutesRequestcountindashboard: 500(simulated)Happytoprovidemoredetailsorrunspecifictestsifhelpful.Thanksformaintainingtheproject!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    🆕 New

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions