Skip to content

Add In-Memory Caching for Affiliate Revenue #1237

@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.

Repository: shapeshift/unchained
Location: node/proxy/api/src/affiliateRevenue/

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:

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

private cache: Map<Address, Events> = new Map()
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
  • Cache key format: ${service}:${txHash}:${timestamp}

Implementation Guidance

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions