Skip to content

⚡ Performance Optimization Guide

📋 Overview

This guide provides comprehensive strategies for optimizing Partner Services API integration performance, including caching, connection pooling, request batching, and monitoring techniques.

Optimization Areas:

  • Response Time - Target <200ms API responses
  • Caching Strategy - Reduce redundant API calls
  • Connection Pooling - Efficient HTTP connections
  • Request Batching - Minimize API calls
  • Error Recovery - Fast failover mechanisms

🎯 Performance Targets

Current Benchmarks

  • API Response Time: <200ms (95th percentile)
  • Authentication Overhead: <5ms
  • Charge Calculation: <150ms
  • Partner Availability Check: <100ms
  • Database Query Performance: <50ms

Scaling Goals

  • Concurrent Users: 500+ supported
  • Daily Shipments: 10,000+ capacity
  • Rate Limits: 1000 requests/minute
  • Cache Hit Rate: >80%

💾 Caching Strategies

1. Multi-Level Caching Architecture

javascript
class MultiLevelCache {
  constructor() {
    this.memoryCache = new Map();
    this.redisCache = new Redis(process.env.REDIS_URL);
    this.cacheConfig = {
      partnerData: { ttl: 1800, levels: ["memory", "redis"] }, // 30 min
      chargeCalculation: { ttl: 900, levels: ["memory", "redis"] }, // 15 min
      zoneData: { ttl: 3600, levels: ["memory", "redis"] }, // 1 hour
      pincodeData: { ttl: 7200, levels: ["memory", "redis"] }, // 2 hours
    };
  }

  async get(key, type = "default") {
    const config = this.cacheConfig[type] || { ttl: 300, levels: ["memory"] };

    // Try memory cache first
    if (config.levels.includes("memory")) {
      const memoryResult = this.memoryCache.get(key);
      if (memoryResult && !this.isExpired(memoryResult)) {
        console.log(`✅ Memory cache hit: ${key}`);
        return memoryResult.data;
      }
    }

    // Try Redis cache
    if (config.levels.includes("redis")) {
      try {
        const redisResult = await this.redisCache.get(key);
        if (redisResult) {
          const parsed = JSON.parse(redisResult);
          if (!this.isExpired(parsed)) {
            console.log(`✅ Redis cache hit: ${key}`);

            // Backfill memory cache
            if (config.levels.includes("memory")) {
              this.memoryCache.set(key, parsed);
            }

            return parsed.data;
          }
        }
      } catch (error) {
        console.warn("Redis cache error:", error.message);
      }
    }

    console.log(`❌ Cache miss: ${key}`);
    return null;
  }

  async set(key, data, type = "default") {
    const config = this.cacheConfig[type] || { ttl: 300, levels: ["memory"] };
    const cacheEntry = {
      data,
      timestamp: Date.now(),
      ttl: config.ttl * 1000,
    };

    // Set in memory cache
    if (config.levels.includes("memory")) {
      this.memoryCache.set(key, cacheEntry);
    }

    // Set in Redis cache
    if (config.levels.includes("redis")) {
      try {
        await this.redisCache.setex(
          key,
          config.ttl,
          JSON.stringify(cacheEntry)
        );
      } catch (error) {
        console.warn("Redis cache set error:", error.message);
      }
    }

    console.log(`💾 Cached: ${key} (TTL: ${config.ttl}s)`);
  }

  isExpired(cacheEntry) {
    return Date.now() - cacheEntry.timestamp > cacheEntry.ttl;
  }
}

// Usage
const cache = new MultiLevelCache();

// Cache partner data
await cache.set(`partner:${partnerId}`, partnerData, "partnerData");

// Retrieve cached data
const cachedPartner = await cache.get(`partner:${partnerId}`, "partnerData");

2. Smart Cache Keys & Invalidation

javascript
class SmartCacheManager {
  constructor(cache) {
    this.cache = cache;
    this.dependencies = new Map();
  }

  // Generate hierarchical cache keys
  generateCacheKey(type, identifiers) {
    const baseKey = `${type}:${Object.values(identifiers).join(":")}`;
    const hash = this.generateHash(identifiers);
    return `${baseKey}:${hash}`;
  }

  // Cache with dependency tracking
  async cacheWithDependencies(key, data, dependencies = [], ttl = 300) {
    await this.cache.set(key, data, ttl);

    // Track dependencies
    dependencies.forEach((dep) => {
      if (!this.dependencies.has(dep)) {
        this.dependencies.set(dep, new Set());
      }
      this.dependencies.get(dep).add(key);
    });
  }

  // Invalidate cache and all dependents
  async invalidateWithDependencies(pattern) {
    const keysToInvalidate = new Set();

    // Find direct matches
    const directKeys = await this.findCacheKeys(pattern);
    directKeys.forEach((key) => keysToInvalidate.add(key));

    // Find dependent keys
    if (this.dependencies.has(pattern)) {
      this.dependencies
        .get(pattern)
        .forEach((key) => keysToInvalidate.add(key));
    }

    // Invalidate all keys
    for (const key of keysToInvalidate) {
      await this.cache.delete(key);
      console.log(`🗑️ Invalidated cache: ${key}`);
    }

    console.log(
      `🧹 Invalidated ${keysToInvalidate.size} cache entries for pattern: ${pattern}`
    );
  }

  generateHash(obj) {
    return require("crypto")
      .createHash("md5")
      .update(JSON.stringify(obj))
      .digest("hex")
      .substring(0, 8);
  }
}

🔗 Connection Pooling & HTTP Optimization

1. HTTP Connection Pool Configuration

javascript
const https = require("https");
const axios = require("axios");

// Optimized HTTPS agent with connection pooling
const httpsAgent = new https.Agent({
  keepAlive: true,
  keepAliveMsecs: 30000, // 30 seconds
  maxSockets: 50, // Per host
  maxFreeSockets: 10, // Keep alive when idle
  timeout: 60000, // 60 seconds
  freeSocketTimeout: 30000, // 30 seconds
  socketActiveTTL: 0, // Disable socket TTL
});

// Configure axios with optimized settings
const apiClient = axios.create({
  httpsAgent,
  timeout: 30000,
  maxRedirects: 3,
  maxContentLength: 50 * 1024 * 1024, // 50MB
  headers: {
    Connection: "keep-alive",
    "Keep-Alive": "timeout=30, max=1000",
  },
});

// Connection pool monitoring
setInterval(() => {
  console.log("🔗 Connection Pool Stats:", {
    totalSockets: httpsAgent.totalSocketCount,
    freeSockets: httpsAgent.freeSockets,
    requests: httpsAgent.requests,
  });
}, 60000); // Every minute

2. Request Compression & Optimization

javascript
// Request/Response compression
apiClient.interceptors.request.use((config) => {
  // Enable compression for large payloads
  if (config.data && JSON.stringify(config.data).length > 1024) {
    config.headers["Content-Encoding"] = "gzip";
    config.headers["Accept-Encoding"] = "gzip, deflate, br";
  }

  return config;
});

// Response decompression
apiClient.interceptors.response.use((response) => {
  // Handle compressed responses
  if (response.headers["content-encoding"]) {
    console.log(
      `📦 Compressed response: ${response.headers["content-encoding"]}`
    );
  }

  return response;
});

📦 Request Batching & Optimization

1. Intelligent Request Batching

javascript
class RequestBatcher {
  constructor(client, options = {}) {
    this.client = client;
    this.batchSize = options.batchSize || 10;
    this.flushInterval = options.flushInterval || 100; // ms
    this.queue = [];
    this.pendingFlush = null;
  }

  async batchRequest(method, path, body, options = {}) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        method,
        path,
        body,
        options,
        resolve,
        reject,
        timestamp: Date.now(),
      });

      this.scheduleFlush();
    });
  }

  scheduleFlush() {
    if (this.pendingFlush) return;

    this.pendingFlush = setTimeout(() => {
      this.flush();
    }, this.flushInterval);

    // Immediate flush if batch is full
    if (this.queue.length >= this.batchSize) {
      clearTimeout(this.pendingFlush);
      this.pendingFlush = null;
      this.flush();
    }
  }

  async flush() {
    if (this.queue.length === 0) return;

    const batch = this.queue.splice(0, this.batchSize);
    this.pendingFlush = null;

    console.log(`🚀 Flushing batch of ${batch.length} requests`);

    // Group similar requests
    const grouped = this.groupRequests(batch);

    for (const [key, requests] of grouped.entries()) {
      try {
        if (requests.length === 1) {
          // Single request
          const req = requests[0];
          const result = await this.client.makeRequest(
            req.method,
            req.path,
            req.body
          );
          req.resolve(result);
        } else {
          // Bulk request
          await this.executeBulkRequest(key, requests);
        }
      } catch (error) {
        // Reject all requests in this group
        requests.forEach((req) => req.reject(error));
      }
    }

    // Schedule next flush if queue has items
    if (this.queue.length > 0) {
      this.scheduleFlush();
    }
  }

  groupRequests(batch) {
    const groups = new Map();

    batch.forEach((request) => {
      const key = `${request.method}:${request.path.split("?")[0]}`;

      if (!groups.has(key)) {
        groups.set(key, []);
      }

      groups.get(key).push(request);
    });

    return groups;
  }

  async executeBulkRequest(key, requests) {
    // Handle specific bulk operations
    if (key.includes("availability/check")) {
      await this.bulkAvailabilityCheck(requests);
    } else if (key.includes("calculate-charges")) {
      await this.bulkChargeCalculation(requests);
    } else {
      // Execute individually for unsupported bulk operations
      for (const req of requests) {
        try {
          const result = await this.client.makeRequest(
            req.method,
            req.path,
            req.body
          );
          req.resolve(result);
        } catch (error) {
          req.reject(error);
        }
      }
    }
  }
}

2. Parallel Request Execution

javascript
class ParallelRequestManager {
  constructor(maxConcurrency = 10) {
    this.maxConcurrency = maxConcurrency;
    this.activeRequests = 0;
    this.requestQueue = [];
  }

  async executeParallel(requests) {
    const results = new Array(requests.length);
    const promises = requests.map((request, index) =>
      this.queueRequest(() => this.executeRequest(request), index)
    );

    const settled = await Promise.allSettled(promises);

    settled.forEach((result, index) => {
      if (result.status === "fulfilled") {
        results[index] = result.value;
      } else {
        results[index] = { error: result.reason };
      }
    });

    return results;
  }

  async queueRequest(requestFn, index) {
    while (this.activeRequests >= this.maxConcurrency) {
      await new Promise((resolve) => {
        this.requestQueue.push(resolve);
      });
    }

    this.activeRequests++;

    try {
      const result = await requestFn();
      return { index, result };
    } finally {
      this.activeRequests--;

      if (this.requestQueue.length > 0) {
        const nextResolve = this.requestQueue.shift();
        nextResolve();
      }
    }
  }
}

📊 Performance Monitoring

1. Real-time Performance Metrics

javascript
class PerformanceMonitor {
  constructor() {
    this.metrics = {
      requests: new Map(),
      responseTime: [],
      errors: [],
      cacheHits: 0,
      cacheMisses: 0,
    };

    this.startTime = Date.now();
  }

  startRequest(requestId, endpoint) {
    this.metrics.requests.set(requestId, {
      endpoint,
      startTime: Date.now(),
      startMemory: process.memoryUsage(),
    });
  }

  endRequest(requestId, success = true, responseSize = 0) {
    const request = this.metrics.requests.get(requestId);
    if (!request) return;

    const endTime = Date.now();
    const duration = endTime - request.startTime;
    const endMemory = process.memoryUsage();

    // Record response time
    this.metrics.responseTime.push({
      endpoint: request.endpoint,
      duration,
      success,
      responseSize,
      memoryDelta: endMemory.heapUsed - request.startMemory.heapUsed,
      timestamp: endTime,
    });

    // Clean up
    this.metrics.requests.delete(requestId);

    // Log slow requests
    if (duration > 1000) {
      console.warn(
        `🐌 Slow request detected: ${request.endpoint} took ${duration}ms`
      );
    }
  }

  recordCacheHit() {
    this.metrics.cacheHits++;
  }

  recordCacheMiss() {
    this.metrics.cacheMisses++;
  }

  getStats() {
    const now = Date.now();
    const recentRequests = this.metrics.responseTime.filter(
      (req) => now - req.timestamp < 60000
    ); // Last minute

    const avgResponseTime =
      recentRequests.length > 0
        ? recentRequests.reduce((sum, req) => sum + req.duration, 0) /
          recentRequests.length
        : 0;

    const successRate =
      recentRequests.length > 0
        ? (recentRequests.filter((req) => req.success).length /
            recentRequests.length) *
          100
        : 100;

    const cacheHitRate =
      this.metrics.cacheHits + this.metrics.cacheMisses > 0
        ? (this.metrics.cacheHits /
            (this.metrics.cacheHits + this.metrics.cacheMisses)) *
          100
        : 0;

    return {
      uptime: now - this.startTime,
      activeRequests: this.metrics.requests.size,
      avgResponseTime: Math.round(avgResponseTime),
      successRate: Math.round(successRate * 100) / 100,
      cacheHitRate: Math.round(cacheHitRate * 100) / 100,
      totalRequests: this.metrics.responseTime.length,
      recentRequests: recentRequests.length,
    };
  }

  generateReport() {
    const stats = this.getStats();

    console.log("\n📊 Performance Report:");
    console.log(`Uptime: ${Math.round(stats.uptime / 1000)}s`);
    console.log(`Active Requests: ${stats.activeRequests}`);
    console.log(`Avg Response Time: ${stats.avgResponseTime}ms`);
    console.log(`Success Rate: ${stats.successRate}%`);
    console.log(`Cache Hit Rate: ${stats.cacheHitRate}%`);
    console.log(`Total Requests: ${stats.totalRequests}`);
    console.log(`Recent Requests (1m): ${stats.recentRequests}\n`);

    return stats;
  }
}

2. Automated Performance Alerts

javascript
class PerformanceAlerting {
  constructor(monitor, options = {}) {
    this.monitor = monitor;
    this.thresholds = {
      avgResponseTime: options.avgResponseTime || 500, // ms
      successRate: options.successRate || 95, // %
      cacheHitRate: options.cacheHitRate || 70, // %
      ...options,
    };

    this.alertCooldown = new Map();
    this.cooldownPeriod = 300000; // 5 minutes
  }

  checkThresholds() {
    const stats = this.monitor.getStats();

    // Check average response time
    if (stats.avgResponseTime > this.thresholds.avgResponseTime) {
      this.sendAlert("HIGH_RESPONSE_TIME", {
        current: stats.avgResponseTime,
        threshold: this.thresholds.avgResponseTime,
      });
    }

    // Check success rate
    if (stats.successRate < this.thresholds.successRate) {
      this.sendAlert("LOW_SUCCESS_RATE", {
        current: stats.successRate,
        threshold: this.thresholds.successRate,
      });
    }

    // Check cache hit rate
    if (stats.cacheHitRate < this.thresholds.cacheHitRate) {
      this.sendAlert("LOW_CACHE_HIT_RATE", {
        current: stats.cacheHitRate,
        threshold: this.thresholds.cacheHitRate,
      });
    }
  }

  sendAlert(type, data) {
    const now = Date.now();
    const lastAlert = this.alertCooldown.get(type);

    if (lastAlert && now - lastAlert < this.cooldownPeriod) {
      return; // Skip due to cooldown
    }

    this.alertCooldown.set(type, now);

    console.warn(`🚨 Performance Alert: ${type}`, data);

    // Send to monitoring system
    this.sendToMonitoringSystem(type, data);
  }

  async sendToMonitoringSystem(type, data) {
    // Implement integration with monitoring systems
    // (Datadog, New Relic, CloudWatch, etc.)
  }
}

🔄 Performance Best Practices

1. API Call Optimization Checklist

  • [ ] Reduce API Calls

    • [ ] Implement comprehensive caching
    • [ ] Use bulk operations when available
    • [ ] Cache partner data for 30+ minutes
    • [ ] Cache zone/pincode data for hours
  • [ ] Connection Optimization

    • [ ] Enable HTTP keep-alive
    • [ ] Use connection pooling
    • [ ] Configure proper timeouts
    • [ ] Enable request/response compression
  • [ ] Request Batching

    • [ ] Batch similar requests together
    • [ ] Implement request queuing
    • [ ] Use parallel execution for independent calls
    • [ ] Optimize batch sizes based on API limits
  • [ ] Error Handling

    • [ ] Implement circuit breakers
    • [ ] Use exponential backoff
    • [ ] Cache error responses appropriately
    • [ ] Provide fallback mechanisms

2. Memory Management

javascript
class MemoryOptimizer {
  constructor() {
    this.gcInterval = setInterval(() => {
      if (global.gc) {
        global.gc();
        console.log("🧹 Manual garbage collection triggered");
      }
    }, 300000); // Every 5 minutes
  }

  monitorMemoryUsage() {
    const usage = process.memoryUsage();
    const thresholds = {
      heapUsed: 500 * 1024 * 1024, // 500MB
      external: 100 * 1024 * 1024, // 100MB
    };

    if (usage.heapUsed > thresholds.heapUsed) {
      console.warn(
        "⚠️ High heap usage:",
        Math.round(usage.heapUsed / 1024 / 1024) + "MB"
      );
    }

    if (usage.external > thresholds.external) {
      console.warn(
        "⚠️ High external memory usage:",
        Math.round(usage.external / 1024 / 1024) + "MB"
      );
    }

    return usage;
  }

  cleanup() {
    if (this.gcInterval) {
      clearInterval(this.gcInterval);
    }
  }
}

🎯 Performance Testing

Load Testing Example

javascript
class LoadTester {
  async runLoadTest(concurrency = 10, duration = 60000) {
    console.log(
      `🧪 Starting load test: ${concurrency} concurrent users, ${duration}ms duration`
    );

    const startTime = Date.now();
    const results = [];
    const promises = [];

    for (let i = 0; i < concurrency; i++) {
      promises.push(this.runUserSimulation(startTime + duration, results));
    }

    await Promise.all(promises);

    return this.analyzeResults(results);
  }

  async runUserSimulation(endTime, results) {
    while (Date.now() < endTime) {
      const startRequest = Date.now();

      try {
        await this.simulateTypicalWorkflow();
        results.push({
          success: true,
          duration: Date.now() - startRequest,
          timestamp: startRequest,
        });
      } catch (error) {
        results.push({
          success: false,
          duration: Date.now() - startRequest,
          timestamp: startRequest,
          error: error.message,
        });
      }

      // Wait between requests (simulate user think time)
      await new Promise((resolve) =>
        setTimeout(resolve, Math.random() * 2000 + 1000)
      );
    }
  }

  async simulateTypicalWorkflow() {
    // Simulate a typical user workflow
    await client.makeRequest("GET", "/health");
    await client.makeRequest("GET", "/partners/comprehensive-data");
    await client.makeRequest("POST", "/partners/availability/check", {
      pickupPincode: "110001",
      deliveryPincode: "400001",
      weight: 2500,
    });
    await client.makeRequest("POST", "/shipments/calculate-charges", {
      partnerId: "test_partner",
      pickupPincode: "110001",
      deliveryPincode: "400001",
      weight: 2500,
    });
  }

  analyzeResults(results) {
    const successful = results.filter((r) => r.success);
    const failed = results.filter((r) => !r.success);

    const avgResponseTime =
      successful.length > 0
        ? successful.reduce((sum, r) => sum + r.duration, 0) / successful.length
        : 0;

    return {
      totalRequests: results.length,
      successfulRequests: successful.length,
      failedRequests: failed.length,
      successRate: (successful.length / results.length) * 100,
      avgResponseTime: Math.round(avgResponseTime),
      p95ResponseTime: this.calculatePercentile(
        successful.map((r) => r.duration),
        95
      ),
      p99ResponseTime: this.calculatePercentile(
        successful.map((r) => r.duration),
        99
      ),
    };
  }

  calculatePercentile(values, percentile) {
    const sorted = values.sort((a, b) => a - b);
    const index = Math.ceil((percentile / 100) * sorted.length) - 1;
    return sorted[index] || 0;
  }
}

🎯 Next Steps

  1. Error Handling - Robust error management
  2. Main System Flow - Complete implementation
  3. Authentication - Secure performance
  4. API Examples - Performance testing strategies

Optimized performance ensures your integration can handle high loads while maintaining excellent user experience! ⚡

Released under the MIT License.