Back to blog

Building Scalable Systems: Lessons from the Trenches

Key architectural patterns and lessons learned from building systems that handle millions of requests. A practical guide to scalability.

Read in Español →

The Scalability Challenge

When your system grows from hundreds to millions of users, everything changes. Database queries that were instant become bottlenecks. APIs that were snappy start timing out. And that monolith you built? It’s now a ticking time bomb.

Here are the patterns that have consistently saved me.

1. Start with the Data Model

Before you think about microservices or Kubernetes, get your data model right. A well-designed schema can postpone the need for complex infrastructure by months or even years.

-- Instead of this
SELECT * FROM users WHERE email LIKE '%@example.com';

-- Do this
CREATE INDEX idx_users_email_domain ON users (split_part(email, '@', 2));
SELECT * FROM users WHERE split_part(email, '@', 2) = 'example.com';

2. Cache Strategically

Not everything needs caching, and not all caching strategies are equal. I follow the “hot path” rule: identify the 20% of endpoints that handle 80% of traffic, and cache those aggressively.

from functools import lru_cache
from redis import Redis

redis = Redis()

def get_user_profile(user_id: str):
    cached = redis.get(f"profile:{user_id}")
    if cached:
        return json.loads(cached)

    profile = db.query(User).filter(User.id == user_id).first()
    redis.setex(f"profile:{user_id}", 300, json.dumps(profile.dict()))
    return profile

3. Async Everything

The moment you have two services talking to each other, introduce a message queue. It’s the single most impactful architectural decision for reliability.

“The best distributed system is one where components don’t need to know about each other.” — Every senior engineer eventually

4. Observability is Not Optional

You can’t scale what you can’t measure. Set up structured logging, distributed tracing, and alerting from day one.

Key Takeaways

  • Start simple, but design for growth
  • Measure before optimizing — gut feelings are often wrong
  • Embrace async patterns — they give you natural backpressure
  • Invest in observability — it pays compound interest

The best architectures aren’t the most complex ones — they’re the ones that are just complex enough.