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