Back to Blog
Case Study

Scaling Mobily: From 10K to 1M+ Links Tracked Monthly

Technical journey of scaling our link analytics platform to handle 500K+ clicks per day with sub-100ms redirect times. Lessons learned from database optimization and caching implementation.

October 28, 2024
10 min read

The Challenge

Mobily started as a simple link shortening tool for marketing teams. Within 6 months, we went from tracking 10,000 links per month to over 1 million links, with 500,000+ clicks processed daily. Our initial architecture couldn't handle this exponential growth.

100x
Growth in 6 Months
500K+
Daily Clicks
<100ms
Redirect Time

Initial Architecture Bottlenecks

1Database Query Performance

Every click required 3 database queries: link lookup, click logging, and analytics aggregation. With millions of records, queries took 500-800ms.

db.links.find({ short_code: "abc123" }) // 600ms average

2No Caching Layer

Popular links were accessed thousands of times daily, but we hit the database for every single request. This created unnecessary load and slow response times.

3Synchronous Click Logging

Analytics data was written synchronously during redirect requests, adding 200-300ms to every click. Users waited while we logged their click data.

Our Scaling Strategy

1. In-Memory Caching Layer

We introduced in-memory caching to cache link data with a 1-hour TTL. This reduced database hits by 95% for popular links and brought average lookup time down from 600ms to 5ms.

// Before: Direct database lookup
const link = await db.links.findOne({ short_code });

// After: Cache-first approach
let link = await cache.get(`link:${short_code}`);
if (!link) {
  link = await db.links.findOne({ short_code });
  await cache.set(`link:${short_code}`, link, 3600);
}

Result:

  • 600ms → 5ms average lookup time
  • 95% reduction in database queries

2. Asynchronous Click Logging

We moved click analytics to a background queue using our job queue system. Redirects now happen immediately while analytics are processed asynchronously.

// Redirect immediately, log analytics later
app.get('/:code', async (req, res) => {
  const link = await getLink(req.params.code);

  // Queue analytics (non-blocking)
  await analyticsQueue.add({
    linkId: link.id,
    ip: req.ip,
    userAgent: req.headers['user-agent'],
    timestamp: Date.now()
  });

  // Redirect immediately
  res.redirect(link.destination);
});

Result:

  • Redirect time: 300ms → 45ms
  • No data loss, better user experience

3. Database Query Optimization

Created compound indexes on frequently queried fields and implemented aggregation pipelines for analytics dashboards.

Index Strategy:

  • • Added index on `short_code` (unique)
  • • Compound index on `(userId, createdAt)` for dashboards
  • • TTL index on analytics collection for auto-cleanup

Result:

  • Dashboard queries: 4s → 150ms
  • 80% reduction in database CPU usage

Final Results

Performance

  • Sub-100ms redirect times (avg 78ms)
  • 500K+ clicks processed daily
  • 1M+ links tracked monthly

Business Impact

  • Serving 50+ marketing agencies
  • 70% cost reduction in infrastructure
  • 99.98% uptime achieved

Key Takeaways

  • 1. Cache aggressively - Most link clicks are on popular links; caching provides massive wins
  • 2. Async everything - Don't make users wait for analytics; queue it
  • 3. Index strategically - Understand query patterns and index accordingly
  • 4. Monitor proactively - Cache memory usage and queue depth are critical metrics

Need Help Scaling Your Platform?

We've successfully scaled multiple SaaS platforms to handle millions of requests. Let's discuss how we can help optimize your application's performance and scalability.