Skip to content

Optimise and add In-Memory Caching for Affiliate Revenue #1246

@premiumjibles

Description

@premiumjibles

Context

The ShapeShift affiliate revenue API aggregates fee data from 6+ external swap provider APIs. Currently, every API request fetches fresh data from all external providers, which is slow (2-10 seconds) and puts unnecessary load on external APIs.

Current Problem:

  • Response time: 2-10 seconds per request
  • Risk of rate limiting from external APIs
  • If one external API is slow, entire response is delayed
  • Same data fetched repeatedly even though it rarely changes

Solution

Implement in-memory caching that:

  1. Pre-loads the last 90 days of data on server startup
  2. Updates incrementally every 5 minutes
  3. Serves requests instantly from cache

Caching Pattern Precedent

The unchained codebase already has caching patterns to follow:

Simple TTL cache (node/proxy/api/src/coingecko.ts):

private requestCache: Partial<Record<string, AxiosResponse>> = {}

Map-based with interval updates (node/coinstacks/arbitrum/api/src/rfox/eventCache.ts):

private cache: Map<Address, Events> = new Map()
// Polling interval: 15 minutes
setInterval(() => this.reindex(), 1000 * 60 * 15)

Acceptance Criteria

  • Create cache class following existing eventCache.ts pattern
  • Initialize cache on server startup in app.ts
  • Load last 90 days of data on initialization
  • Update incrementally every 5 minutes
  • Use 2-minute overlap on incremental fetches to handle eventual consistency
  • Deduplicate entries by ${service}:${txHash}:${timestamp} key
  • Modify endpoint to read from cache instead of fetching live
  • Fallback to live fetch if cache not yet initialized
  • Add logging for cache initialization and update status

Files to Create/Modify

  • node/proxy/api/src/affiliateRevenue/cache.ts (new file)
  • node/proxy/api/src/affiliateRevenue/index.ts (use cache)
  • node/proxy/api/src/app.ts (initialize cache on startup)

Configuration

  • Initial load: 90 days of historical data
  • Update interval: 5 minutes
  • Overlap for incremental updates: 2 minutes (to catch late-appearing transactions)
  • Cache key format: ${service}:${txHash}:${timestamp}

Implementation Guidance

// cache.ts
class AffiliateRevenueCache {
  private cache: Map<string, Fees> = new Map()
  private lastUpdated: number = 0
  private initialized: boolean = false

  async initialize() {
    const ninetyDaysAgo = Math.floor(Date.now() / 1000) - (90 * 24 * 60 * 60)
    await this.fetchAndStore(ninetyDaysAgo, Math.floor(Date.now() / 1000))
    this.initialized = true

    setInterval(() => this.incrementalUpdate(), 5 * 60 * 1000)
  }

  private async incrementalUpdate() {
    const since = this.lastUpdated - 120 // 2 min overlap
    await this.fetchAndStore(since, Math.floor(Date.now() / 1000))
    this.lastUpdated = Math.floor(Date.now() / 1000)
  }

  getRevenue(start: number, end: number): AffiliateRevenueResponse {
    if (!this.initialized) throw new Error('Cache not initialized')
    // Filter cache by time range and aggregate
  }
}

export const affiliateRevenueCache = new AffiliateRevenueCache()

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions