DEV Community

gleamso
gleamso

Posted on

How I Optimized OG Image Generation: A Performance Case Study πŸ“Š

Hey dev friends! πŸ‘‹ Following our Making OpenGraph Work, let's dive into a real performance optimization journey. Here's what happened when I needed to optimize gleam.so's OG image generation from 2.5s to under 500ms.

Initial State: The Problem πŸ“‰

When I first launched gleam.so, performance wasn't great:

Initial Metrics:
- Average generation time: 2.5s
- P95 generation time: 4.2s
- Memory usage: ~250MB per image
- Cache hit rate: 35%
- Failed generations: 8%
Enter fullscreen mode Exit fullscreen mode

Users were noticing:

"Preview takes too long to load"
"Sometimes images don't generate at all"
"System feels sluggish"

Measurement Setup πŸ“

First, I set up proper monitoring:

interface PerformanceMetrics {
  generation: {
    duration: number;    // Total time
    steps: {            // Step-by-step timing
      template: number;
      render: number;
      optimize: number;
      store: number;
    };
    memory: number;     // Memory usage
    success: boolean;   // Success/failure
  };
  cache: {
    hit: boolean;       // Cache hit/miss
    duration: number;   // Cache operation time
  };
}

// Monitoring implementation
const monitor = new PerformanceMonitor({
  metrics: ['generation', 'cache', 'memory'],
  interval: '1m',
  retention: '30d'
});
Enter fullscreen mode Exit fullscreen mode

The Optimization Journey πŸ› οΈ

1. Template Preprocessing

Before:

// Parsing templates on every request
const renderTemplate = async (template, data) => {
  const parsed = await parseTemplate(template);
  return renderImage(parsed, data);
};
Enter fullscreen mode Exit fullscreen mode

After:

// Precompiled templates
const templateCache = new Map<string, CompiledTemplate>();

const renderTemplate = async (templateId, data) => {
  if (!templateCache.has(templateId)) {
    templateCache.set(
      templateId,
      await compileTemplate(templates[templateId])
    );
  }
  return renderImage(templateCache.get(templateId), data);
};

// Result:
// - 300ms saved per generation
// - 40% less memory usage
Enter fullscreen mode Exit fullscreen mode

2. Multi-Layer Caching

class OGImageCache {
  constructor() {
    this.memory = new QuickLRU({ maxSize: 100 });
    this.redis = new Redis(process.env.REDIS_URL);
    this.cdn = new CloudflareKV('og-images');
  }

  async get(key: string): Promise<Buffer | null> {
    // 1. Check memory cache
    const memoryCache = this.memory.get(key);
    if (memoryCache) return memoryCache;

    // 2. Check Redis
    const redisCache = await this.redis.get(key);
    if (redisCache) {
      this.memory.set(key, redisCache);
      return redisCache;
    }

    // 3. Check CDN
    const cdnCache = await this.cdn.get(key);
    if (cdnCache) {
      await this.warmCache(key, cdnCache);
      return cdnCache;
    }

    return null;
  }
}

// Result:
// - Cache hit rate: 35% β†’ 85%
// - Average response time: 2.5s β†’ 800ms
Enter fullscreen mode Exit fullscreen mode

3. Resource Optimization

// Before: Loading fonts per request
const loadFonts = async () => {
  return Promise.all(
    fonts.map(font => fetch(font.url).then(res => res.arrayBuffer()))
  );
};

// After: Preloaded fonts
const FONTS = {
  inter: fs.readFileSync('./fonts/Inter.ttf'),
  roboto: fs.readFileSync('./fonts/Roboto.ttf')
};

// Result:
// - Font loading: 400ms β†’ 0ms
// - Memory usage: -30%
Enter fullscreen mode Exit fullscreen mode

4. Parallel Processing

// Before: Sequential processing
const generateOG = async (template, data) => {
  const image = await render(template, data);
  const optimized = await optimize(image);
  const stored = await store(optimized);
  return stored;
};

// After: Parallel processing
const generateOG = async (template, data) => {
  const [image, resources] = await Promise.all([
    render(template, data),
    loadResources(template)
  ]);

  const [optimized, stored] = await Promise.all([
    optimize(image),
    prepareStorage()
  ]);

  return finalize(optimized, stored);
};

// Result:
// - 30% faster generation
// - Better resource utilization
Enter fullscreen mode Exit fullscreen mode

Current Performance πŸ“ˆ

After these optimizations:

Current Metrics:
- Average generation time: 450ms (-82%)
- P95 generation time: 850ms (-80%)
- Memory usage: 90MB (-64%)
- Cache hit rate: 85% (+50%)
- Failed generations: 0.5% (-7.5%)
Enter fullscreen mode Exit fullscreen mode

Key Learnings πŸŽ“

  1. Measurement is Critical

    • Set up monitoring first
    • Track detailed metrics
    • Make data-driven decisions
  2. Cache Strategically

    • Multiple cache layers
    • Smart invalidation
    • Warm cache for popular items
  3. Resource Management

    • Preload where possible
    • Optimize memory usage
    • Parallel processing
  4. Error Handling

    • Graceful degradation
    • Detailed error tracking
    • Automatic recovery

Implementation Tips πŸ’‘

  1. Start with Monitoring
// Simple but effective monitoring
const track = metrics.track('og_generation', {
  duration: endTime - startTime,
  memory: process.memoryUsage().heapUsed,
  success: !error,
  cached: !!cacheHit
});
Enter fullscreen mode Exit fullscreen mode
  1. Cache Wisely
// Generate deterministic cache keys
const getCacheKey = (template, data) => {
  return crypto
    .createHash('sha256')
    .update(`${template.id}-${JSON.stringify(data)}`)
    .digest('hex');
};
Enter fullscreen mode Exit fullscreen mode
  1. Handle Errors Gracefully
// Always provide a fallback
const generateWithFallback = async (template, data) => {
  try {
    return await generateOG(template, data);
  } catch (error) {
    metrics.trackError(error);
    return generateFallback(template, data);
  }
};
Enter fullscreen mode Exit fullscreen mode

Try It Yourself! πŸš€

I've implemented all these optimizations in gleam.so, and for Black Friday, you can try the optimized system at 75% off! Generate blazing-fast OG images without worrying about performance.

Share Your Experience 🀝

  • What performance challenges have you faced with OG images?
  • Which optimization techniques worked for you?
  • Any tips to share with the community?

Let's discuss in the comments!


*This is part of the "Making OpenGraph Work" series. *

Top comments (0)

The Most Contextual AI Development Assistant

Pieces.app image

Our centralized storage agent works on-device, unifying various developer tools to proactively capture and enrich useful materials, streamline collaboration, and solve complex problems through a contextual understanding of your unique workflow.

πŸ‘₯ Ideal for solo developers, teams, and cross-company projects

Learn more

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❀️