Skip to content

feat(perps): disk-backed cold-start cache for instant data display#27898

Merged
abretonc7s merged 67 commits into
mainfrom
feat/perps/simplify-cache-mechanism
Apr 11, 2026
Merged

feat(perps): disk-backed cold-start cache for instant data display#27898
abretonc7s merged 67 commits into
mainfrom
feat/perps/simplify-cache-mechanism

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Description

Implements TAT-2761: remove the PerpsHome cold-start skeleton caused by empty controller caches.

What changed:

  • PerpsAlwaysOnProvider now owns startMarketDataPreload(), so preload runs from the wallet root in both wallet layouts.
  • Perps hooks seed first render from live stream snapshots before falling back to controller cache, eliminating remount flashes for positions, orders, account, and markets.
  • MarketDataChannel.clearCache() now preserves valid global market cache on account-only clears.
  • PerpsController persists successful preload market/user snapshots to MMKV and hydrates them synchronously on startup, including multi-provider payloads.
  • docs/perps/perps-caching-architecture.md updated to match the actual disk write and hydration paths.

Benchmark (iOS simulator, April 1 2026):

  • Clean perps disk cache → first PerpsHome market data: 3908ms
  • Same-session reopen of PerpsHome → first market data: 0ms
  • After JS reload → controller hydrated 286 markets in 9ms, first PerpsHome market data: 0ms

Changelog

CHANGELOG entry: Added persistent disk-backed caching for Perps so the trading screen opens instantly without loading skeletons

Related issues

Fixes: TAT-2761

Manual testing steps

Feature: Perps cold-start caching

  Scenario: user opens Perps after a fresh app launch
    Given the user has previously visited the Perps trading screen
    When user relaunches the app and navigates to Perps
    Then the trading screen displays market data immediately without loading skeletons

  Scenario: user reopens Perps after JS reload
    Given the user is on the Perps trading screen
    When user triggers a JS reload and navigates back to Perps
    Then cached market data is hydrated instantly from disk
    And no skeleton loading state is shown

  Scenario: user switches accounts while on Perps
    Given the user has Perps open with cached market data
    When user switches to a different account
    Then global market cache is preserved
    And only user-specific data (positions, orders) is refreshed

Screenshots/Recordings

Before

Loading skeletons shown on every PerpsHome open (cold start and JS reload).

After

Market data renders instantly from disk cache; no skeleton flash on reopen or JS reload.

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Medium risk because it changes Perps data seeding, cache invalidation, and persistence behavior across app restarts and reconnects; bugs could surface as stale/incorrect positions/markets or missing invalidations during account/provider switches.

Overview
Enables instant Perps home rendering on cold start by adding a disk-backed cache layer and seeding hooks from synchronous stream snapshots before falling back to controller preload caches.

PerpsController now hydrates market/user caches from disk at construction time and persists successful REST preloads back to disk, with support for aggregated multi-provider payloads and a new skipTTL option to serve disk-hydrated data on first paint.

PerpsStreamManager adds getSnapshot() to channels and persists live market/user snapshots to disk (throttled), preserves global market cache on account-only clears, and resets/invalidates disk cache on reconnect/account/provider changes. Preload ownership moves from Wallet/index.tsx to PerpsAlwaysOnProvider, and tests/mocks/docs are updated accordingly.

Reviewed by Cursor Bugbot for commit ed83a41. Bugbot is set up for automated code reviews on this repo. Configure here.

Write stream channel snapshots (market data, positions, orders, account)
to MMKV after first WS data arrives and read them on next cold start,
eliminating the 1-3s skeleton gap before WebSocket data arrives.

- Add diskCache DI interface to PerpsPlatformDependencies
- Implement via StorageWrapper (MMKV) in mobileInfrastructure adapter
- Hydrate in-memory caches from disk in startMarketDataPreload()
- Throttled (30s) disk writes from stream channel callbacks
- Invalidate on account switch, provider change, and network change
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamaskbot metamaskbot added team-perps Perps team INVALID-PR-TEMPLATE PR's body doesn't match template labels Mar 25, 2026
…odology

Temporary [PERPS_BENCH] markers + helper methods for measuring disk cache
performance. Includes:
- Hydration timing markers in PerpsController
- Hook mount cache-hit markers in usePerpsMarkets
- clearDiskCache/clearInMemoryCaches/seedDiskCache temp methods
- Benchmark recipe (benchmark-cold-start-cache.json)
- Methodology doc with Android Pixel 6a + iOS simulator results
Rewrite cold-start benchmark to simulate real user interaction:
- Replace artificial stop/startMarketDataPreload with WS disconnect()
- Add cleanupPrewarm() to kill controller-level price subscription
- Check stream cache (what user sees) instead of controller cache
- Nav-away-first ordering matches real backgrounding flow

Benchmark results (283 markets):
- Android (Pixel 6a): baseline 1048ms → disk cache 0ms (hydration 65ms)
- iOS (Simulator): baseline 441ms → disk cache 0ms (hydration 45ms)
…k bridge

- getCachedMarketDataForActiveProvider({ skipTTL }) bypasses 5-min TTL
  so disk-hydrated structural data renders on mount
- getCachedUserDataForActiveProvider({ skipTTL }) bypasses 60s staleness
- Strip volatile price fields on disk hydration (show $--- until WS)
- hasCachedPerpsData uses skipTTL: true for initial render
- purgeCache/purgeAllCaches for benchmark cache isolation
- __PERPS_STREAM__ bridge (__DEV__ only) for CDP benchmark access
@github-actions github-actions Bot added size-XL and removed size-M labels Mar 25, 2026
Remove all [PERPS_BENCH] markers, purgeCache/purgeAllCaches methods,
__PERPS_STREAM__ global bridge, seedDiskCache, hydrateCacheFromDiskOnly,
clearInMemoryCaches, and clearDiskCache temporary methods.

Hydration debug logs retained with standard PerpsController prefix.

To reproduce benchmark: checkout 9594b2c and run:
  bash scripts/perps/agentic/validate-recipe.sh \
    scripts/perps/agentic/teams/perps/recipes/benchmark-cold-start-cache.json
@abretonc7s abretonc7s marked this pull request as ready for review March 25, 2026 05:40
@abretonc7s abretonc7s requested a review from a team as a code owner March 25, 2026 05:41
@metamaskbot metamaskbot removed the INVALID-PR-TEMPLATE PR's body doesn't match template label Mar 25, 2026
Comment thread app/components/UI/Perps/providers/PerpsStreamManager.tsx Outdated
Comment thread app/components/UI/Perps/services/PerpsConnectionManager.ts
Cover the new skipTTL option on getCachedMarketDataForActiveProvider and
getCachedUserDataForActiveProvider, the #hydrateCacheFromDisk flow
(market hydration with price stripping, user data address matching,
fresher-data guard, corrupt JSON resilience), and the diskCache adapter
in mobileInfrastructure.

New code coverage: 90% (76/84 changed lines).
@github-actions github-actions Bot added the risk-medium Moderate testing recommended · Possible bug introduction risk label Mar 25, 2026
- Remove 001-disk-backed-cold-start-cache.md (proposal doc not meant
  for the repo — content lives in JIRA TAT-2761)
- Add docs/perps/perps-caching-architecture.md updated for disk cache:
  new Disk tier, MMKV cold-start hydration flow, cache clearing matrix
  with disk column, Phase 1 marked as implemented
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.10526% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.54%. Comparing base (9564fd6) to head (7700407).
⚠️ Report is 15 commits behind head on main.

Files with missing lines Patch % Lines
...mponents/UI/Perps/providers/PerpsStreamManager.tsx 66.66% 9 Missing and 3 partials ⚠️
app/controllers/perps/PerpsController.ts 88.37% 2 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #27898      +/-   ##
==========================================
+ Coverage   82.52%   82.54%   +0.01%     
==========================================
  Files        4800     4826      +26     
  Lines      123748   123966     +218     
  Branches    27576    27621      +45     
==========================================
+ Hits       102122   102323     +201     
- Misses      14567    14580      +13     
- Partials     7059     7063       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added risk-medium Moderate testing recommended · Possible bug introduction risk and removed risk-medium Moderate testing recommended · Possible bug introduction risk labels Mar 25, 2026
…ve global ref

- Add .catch() to StorageWrapper.removeItem calls in ConnectionManager
- Extract getProviderNetworkKey() helper to eliminate 5 duplicated template literals
- Replace _streamManagerRef global with onDataPersist callback on StreamChannel base class
- Delete dead benchmark-cold-start-cache.json recipe (references removed methods)
@github-actions github-actions Bot added size-L risk-medium Moderate testing recommended · Possible bug introduction risk and removed size-XL risk-medium Moderate testing recommended · Possible bug introduction risk labels Mar 25, 2026
Comment thread app/components/UI/Perps/providers/PerpsStreamManager.tsx Outdated
…ersist

Prevents consuming the 30s throttle window when validation checks
(empty snapshot, missing address) cause an early return without writing.
@github-actions github-actions Bot removed the risk-medium Moderate testing recommended · Possible bug introduction risk label Mar 25, 2026
Comment thread app/components/UI/Perps/hooks/usePerpsHomeData.ts
abretonc7s and others added 2 commits April 9, 2026 20:43
Replace removed per-field cache entries (cachedMarketData,
cachedAccountState, etc.) with the new by-provider maps
(cachedMarketDataByProvider, cachedUserDataByProvider) and add
missing withdrawal tracking fields. Remove stale ignored keys
from fixture-validation.ts.
aganglada
aganglada previously approved these changes Apr 9, 2026
racitores
racitores previously approved these changes Apr 9, 2026
Comment thread app/components/UI/Perps/adapters/mobileInfrastructure.test.ts
The destructured `clearCache` property doesn't exist on all union
members of mockStreamManagerInstance values. Use an `in` guard to
narrow the type before accessing it.
Comment thread app/components/UI/Perps/providers/__mocks__/PerpsStreamManager.tsx
Comment thread app/components/UI/Perps/adapters/mobileInfrastructure.test.ts
Comment thread app/components/UI/Perps/adapters/mobileInfrastructure.test.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ccef9a2. Configure here.

Comment thread tests/framework/fixtures/json/default-fixture.json Outdated
racitores
racitores previously approved these changes Apr 10, 2026
Keep simplified empty cache objects from feature branch,
discard populated cache data and cachedUserDataTimestamp from main.
geositta
geositta previously approved these changes Apr 10, 2026
The cached market/user data is live-fetched and varies between runs,
so it should be ignored like other non-deterministic state.
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePerps, SmokeWalletPlatform, SmokeConfirmations
  • Selected Performance tags: @PerformancePreps, @PerformanceLaunch
  • Risk Level: high
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
This PR introduces a significant architectural change to the Perps feature's caching system:

  1. PerpsController disk cache hydration: A new #hydrateCacheFromDiskSync() method is called at controller construction time, reading MMKV disk data synchronously to pre-populate in-memory caches before React components mount. This is a fundamental change to how Perps data is initialized.

  2. New perpsDiskPersistence.ts utility: Handles reading/writing market and user data to MMKV disk storage for cold-start performance.

  3. PerpsStreamManager getSnapshot() methods: All stream channels now expose getSnapshot() for synchronous cold-start reads. Hooks (usePerpsMarkets, usePerpsLiveAccount, usePerpsLiveOrders, usePerpsLivePositions) now call getSnapshot() on first render instead of only using getPreloadedData.

  4. PerpsAlwaysOnProvider lifecycle change: startMarketDataPreload/stopMarketDataPreload was moved from Wallet/index.tsx to PerpsAlwaysOnProvider, changing when preloading starts/stops.

  5. E2E mock updates: perps-controller-mixin.ts adds getSnapshot() to all mock channels to prevent crashes. Without this fix, E2E tests would throw TypeError: undefined is not a function when wallet home mounts.

  6. Fixture changes: default-fixture.json removes the large cachedMarketDataByProvider blob; fixture-validation.ts adds these fields to ignored keys.

  7. storage-wrapper.ts: New getItemSync() method (returns null in E2E environments).

SmokePerps: Primary tag - all perps functionality (market list, positions, orders, add funds) is affected by the caching architecture changes. The E2E mock was specifically updated to support new getSnapshot() calls.

SmokeWalletPlatform: Perps is embedded in the Trending tab (per tag description). The Wallet/index.tsx change removes the preload lifecycle management, and PerpsAlwaysOnProvider now handles it. This could affect wallet home rendering and the Trending/Perps section.

SmokeConfirmations: Per SmokePerps tag description, when selecting SmokePerps, also select SmokeConfirmations (Add Funds deposits are on-chain transactions).

Performance Test Selection:
This PR is specifically a performance optimization (cold-start disk cache hydration). @PerformancePreps is directly relevant as the perps market loading, position management, and add funds flow are all affected by the new disk cache hydration system. @PerformanceLaunch is relevant because the PerpsController now performs synchronous MMKV reads at construction time during app initialization, which could impact cold start time. The entire purpose of this PR is to eliminate skeleton flashes and improve first-render performance for Perps data.

View GitHub Actions results

@github-actions

Copy link
Copy Markdown
Contributor

E2E Fixture Validation — Schema is up to date
11 value mismatches detected (expected — fixture represents an existing user).
View details

@sonarqubecloud

Copy link
Copy Markdown

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

Labels

agentic release-7.74.0 Issue or pull request that will be included in release 7.74.0 risk-high Extensive testing required · High bug introduction risk size-XL team-perps Perps team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants