Performance Optimization Guide

Performance Optimization Guide

Guide to optimizing SCORM API performance and reducing latency.

Table of Contents

Overview

Performance optimization focuses on:

  • Reducing API response times
  • Minimizing package processing time
  • Optimizing session management
  • Efficient resource usage
  • Scalability improvements

API Optimization

Request Optimization

1. Use Pagination:

// ❌ Fetching all packages
const allPackages = await fetch('/api/v1/packages?tenant_id=...');

// ✅ Use pagination
const packages = await fetch(
  '/api/v1/packages?tenant_id=...&page=1&limit=20'
);

2. Filter Results:

// ✅ Filter at API level
const completedSessions = await fetch(
  '/api/v1/sessions?completion_status=completed&limit=50'
);

3. Select Specific Fields:

// ✅ Request only needed fields
const packages = await fetch(
  '/api/v1/packages?fields=id,title,version'
);

Batch Operations

Batch Session Updates:

// ❌ Multiple individual requests
for (const session of sessions) {
  await updateSession(session.id, updates);
}

// ✅ Batch update (if supported)
await batchUpdateSessions(sessions.map(s => ({
  id: s.id,
  updates: s.updates
})));

Connection Pooling

Reuse Connections:

// Use connection pool
const client = new APIClient({
  baseURL: 'https://scorm-api.allurelms.com',
  keepAlive: true,
  maxConnections: 10
});

Package Processing Optimization

Package Size Optimization

1. Compress Media:

  • Optimize images (WebP, compressed JPEG)
  • Compress videos (H.264, appropriate bitrate)
  • Minimize file sizes

2. Remove Unnecessary Files:

  • Remove source files
  • Remove development files
  • Remove duplicate assets

3. Optimize Structure:

  • Flat directory structure
  • Minimize nesting
  • Efficient file organization

Processing Time

Expected Processing Times:

  • Small packages (<10MB): 10-30 seconds
  • Medium packages (10-100MB): 30-120 seconds
  • Large packages (100MB-1GB): 2-10 minutes
  • Very large packages (>1GB): 10-30 minutes

Optimization Tips:

  • Use multipart upload for large packages
  • Process asynchronously with webhooks
  • Monitor processing status
  • Set appropriate timeouts

Session Management Optimization

Session Creation

1. Reuse Sessions:

// Check for existing incomplete session
const existingSession = await findIncompleteSession(
  packageId,
  userId
);

if (existingSession) {
  return existingSession; // Reuse
}

// Create new only if needed
return await createSession(packageId, userId);

2. Batch Session Creation:

// Create multiple sessions efficiently
const sessions = await Promise.all(
  userIds.map(userId => createSession(packageId, userId))
);

Progress Tracking

1. Reduce Update Frequency:

// ❌ Update on every CMI change
scormAPI.setValue('cmi.core.lesson_status', 'incomplete');
await updateSession(); // Too frequent

// ✅ Batch updates
const updates = [];
// Collect updates
updates.push({ field: 'cmi.core.lesson_status', value: 'incomplete' });
// Update periodically (e.g., every 5 seconds)
setInterval(() => updateSession(updates), 5000);

2. Use Webhooks Instead of Polling:

// ❌ Polling every second
setInterval(async () => {
  const session = await getSession(sessionId);
  // Check status
}, 1000);

// ✅ Use webhooks
await subscribeToWebhook('session.completed', (event) => {
  // Handle completion
});

Caching Strategies

Client-Side Caching

1. Cache Package Metadata:

const packageCache = new Map();

async function getPackage(packageId: string) {
  if (packageCache.has(packageId)) {
    return packageCache.get(packageId);
  }
  
  const pkg = await fetchPackage(packageId);
  packageCache.set(packageId, pkg);
  return pkg;
}

2. Cache Session Data:

// Cache session data with TTL
const sessionCache = new Map();

async function getSession(sessionId: string) {
  const cached = sessionCache.get(sessionId);
  if (cached && cached.expiresAt > Date.now()) {
    return cached.data;
  }
  
  const session = await fetchSession(sessionId);
  sessionCache.set(sessionId, {
    data: session,
    expiresAt: Date.now() + 60000 // 1 minute TTL
  });
  return session;
}

HTTP Caching

Use ETags:

const response = await fetch('/api/v1/packages/pkg_abc123', {
  headers: {
    'X-API-Key': apiKey,
    'If-None-Match': cachedETag // Use cached ETag
  }
});

if (response.status === 304) {
  // Use cached data
  return cachedData;
}

Database Optimization

Query Optimization

1. Use Indexes:

-- Indexes on frequently queried columns
CREATE INDEX idx_packages_tenant_id ON scorm_packages(tenant_id);
CREATE INDEX idx_sessions_user_package ON scorm_sessions(user_id, package_id);

2. Limit Result Sets:

// Always use LIMIT
const sessions = await db.sessions.findMany({
  where: { tenant_id: tenantId },
  take: 20, // Limit results
  orderBy: { created_at: 'desc' }
});

3. Select Only Needed Fields:

// Select only required fields
const packages = await db.packages.findMany({
  where: { tenant_id: tenantId },
  select: {
    id: true,
    title: true,
    version: true
    // Don't select large fields unless needed
  }
});

Best Practices

1. Minimize API Calls

// ❌ Multiple calls
const package = await getPackage(packageId);
const sessions = await getSessions(packageId);
const reports = await getReports(packageId);

// ✅ Single call with related data
const data = await getPackageWithDetails(packageId);

2. Use Async Processing

// Process package asynchronously
const upload = await uploadPackage(file);
// Don't wait for processing
await subscribeToWebhook('package.processing.completed', (event) => {
  // Handle completion
});

3. Implement Retry Logic

async function fetchWithRetry(url: string, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetch(url);
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await delay(Math.pow(2, i) * 1000);
    }
  }
}

4. Monitor Performance

// Track API call performance
async function trackPerformance(fn: () => Promise<any>) {
  const start = Date.now();
  const result = await fn();
  const duration = Date.now() - start;
  
  console.log(`Operation took ${duration}ms`);
  
  if (duration > 5000) {
    console.warn('Slow operation detected');
  }
  
  return result;
}

Related Documentation


Last Updated: 2025-01-15