Skip to main content
Madhukar
All Articles

Building Scalable Systems: Caching, Rate Limiting and Observability

July 17, 20265 min read
System DesignScalabilityRedisDevops
Building Scalable Systems: Caching, Rate Limiting and Observability

What happens when your application suddenly gets 1 million users?

A ticket-booking site opens sales for a sold-out concert. A product launch goes viral overnight. A flash sale starts at midnight and every notified user opens the app in the same sixty seconds.

None of these are edge cases anymore — they’re Tuesday for a successful product. What separates a system that survives them from one that falls over isn’t more servers thrown at the problem after the fact — it’s three architectural layers built in from the start: caching to avoid redoing expensive work, rate limiting to protect the system from being overwhelmed, and observability to actually understand what’s happening while it’s happening. This guide builds all three, together, into one coherent picture.

1. Why Applications Need to Scale

What scalability means

Scalability is a system’s ability to handle growing load — more users, more requests, more data — without a proportional collapse in performance or reliability.

Vertical vs horizontal scaling

Vertical scaling means making a single server more powerful — more CPU, more RAM. It’s simple, but has a hard ceiling and a single point of failure. Horizontal scaling means adding more servers to share the load. It has far higher ceilings but requires the architecture — caching, load balancing, session handling — to actually support running many instances at once.

Common bottlenecks in web applications

  • A single database handling every read and write, including repeated identical queries
  • No protection against a single client or bot sending excessive requests
  • No visibility into which part of the system is actually struggling under load

Performance challenges as users grow

The relationship between “more users” and “more load” isn’t always linear — a flash sale doesn’t just add more of the same traffic, it concentrates enormous demand onto the same few resources (one product page, one checkout flow) simultaneously.

Why architecture matters

Adding more servers alone doesn’t fix an architecture that redoes the same expensive database query for every single user, or has no defense against a traffic spike. Scalability is largely an architectural property, not just a hardware one.

2. Understanding Caching

What caching is

Caching stores the result of expensive work — a database query, a computed value, a rendered page — so a future request for the same thing can be served instantly, without redoing that work.

Why repeated computation is expensive

If a product page’s details are fetched from the database on every single page view, a viral product can mean the same identical query runs thousands of times a second — pure, avoidable repeated work.

Cache hit vs cache miss

A cache hit means the requested data was already in the cache — served instantly. A cache miss means it wasn’t, so the system has to do the expensive work (query the database), then usually store the result in the cache for next time.

Common cache locations

  • Browser cache — stores assets (images, scripts) directly on the user’s device, avoiding a network request entirely on repeat visits
  • CDN cache — stores content at servers geographically close to users, reducing latency and load on the origin server
  • Application cache — an in-memory store (like Redis) sitting in front of the database, for frequently accessed data
  • Database cache — the database’s own internal caching of frequently run queries or accessed rows

Benefits of caching

Dramatically reduced load on the database and backend, much faster response times for users, and — critically during a traffic spike — the difference between a system that gracefully absorbs demand and one that falls over.

3. Cache Invalidation Challenges

Why cache invalidation is difficult

There’s a well-known saying in computer science: there are only two hard problems — cache invalidation and naming things. Caching is easy; knowing exactly when cached data has become outdated and needs refreshing is genuinely hard.

Stale data problems

If a product’s price changes but the cache still holds the old value, users see incorrect information until the cache is refreshed — a real problem for anything time-sensitive, like inventory counts during a flash sale.

Cache consistency

In distributed systems with multiple cache layers (browser, CDN, application), keeping all of them consistent with the real, current data is an ongoing coordination challenge, not a one-time setup.

Tradeoffs between freshness and speed

This is the fundamental tension underlying every caching decision: cache aggressively, and you serve stale data longer; invalidate aggressively, and you lose much of caching’s performance benefit by constantly re-fetching. Nearly every caching strategy is really a specific answer to this same tradeoff.

4. Advanced Cache Protection Techniques

TTL (Time To Live)

TTL sets an expiration time on cached data — after which it’s automatically considered stale and refreshed on the next request. Simple, predictable, and the most common baseline caching strategy.

Jitter on TTL

If many cache entries are all set to expire at exactly the same moment (say, everything cached at server startup, all with a 1-hour TTL), they all expire simultaneously — causing a sudden burst of cache misses hitting the database at once. Jitter adds a small, randomized variation to each entry’s TTL, spreading expirations out over time instead of all at once.

Probabilistic Early Expiration

Instead of waiting for a cache entry to fully expire before refreshing it, this technique probabilistically refreshes it slightly before expiration — with a chance that increases as expiration approaches. This smooths out the refresh load instead of concentrating it at the exact expiration moment.

Mutex Locking

When a popular cache entry expires, many simultaneous requests can all get a cache miss at once and all try to regenerate it simultaneously — a phenomenon called a cache stampede. Mutex locking ensures only one request regenerates the value while the rest wait briefly (or serve slightly stale data) instead of all hammering the database at once.

Stale While Revalidate

This pattern serves the existing (slightly stale) cached value immediately, while triggering a background refresh for the next request — users get instant responses, and freshness catches up shortly after, without anyone waiting on the slow path.

Cache Warming

Rather than waiting for the first real user request to populate the cache (and eat that first slow request), cache warming proactively pre-loads expected-to-be-popular data before traffic arrives — commonly done ahead of a known event like a product launch or flash sale.

5. Rate Limiting

What rate limiting is

Rate limiting restricts how many requests a client (a user, an IP address, an API key) can make within a given time period — protecting the system from being overwhelmed, whether by accident or by abuse.

Why APIs need protection

Without limits, a single misbehaving script, a bug in a client causing retry loops, or a deliberate abuse attempt can consume a disproportionate share of a system’s capacity — degrading service for everyone else.

Preventing abuse and traffic spikes

Rate limiting is one of the most direct defenses against both malicious abuse (scraping, credential-stuffing attacks) and accidental overload (a buggy client hammering an endpoint in a retry loop).

Common rate limiting strategies

Fixed Window — counts requests within fixed time blocks (e.g., “100 requests per minute, reset every minute exactly on the clock”). Simple, but allows a burst of traffic right at the boundary between two windows to briefly exceed the intended rate.

Sliding Window — instead of a fixed clock boundary, continuously considers a rolling time window ending “now” — smoothing out the boundary-burst problem fixed windows have.

Token Bucket — a bucket holds a limited number of tokens, refilled at a steady rate; each request consumes a token, and requests are rejected once the bucket is empty. This naturally allows brief bursts (as long as tokens are available) while enforcing a steady average rate over time.

Leaky Bucket — requests are added to a queue (the “bucket”) and processed at a fixed, steady rate, regardless of how bursty the incoming traffic is — smoothing traffic out into a consistent outgoing rate, at the cost of potentially higher latency during bursts.

6. Protecting Systems During Traffic Surges

Sudden traffic spikes

A flash sale or viral moment can multiply normal traffic by 10x or more within seconds — architecture built only for average load has no chance without additional protection layers.

API abuse

Beyond legitimate spikes, systems face deliberate abuse: scrapers, credential-stuffing bots, or automated ticket-buying scripts during high-demand releases — rate limiting is a core defense here too.

Bot traffic

Distinguishing legitimate surge traffic from automated bot traffic often requires combining rate limiting with additional signals (request patterns, headers, behavioral analysis) — rate limiting alone catches volume-based abuse, not all abuse.

Preventing cascading failures

A cascading failure happens when one overwhelmed component (say, a slow database) causes upstream services to also become overwhelmed while waiting on it, which then affects their upstream callers — a chain reaction that can take down an entire system from a single weak point. Caching and rate limiting both work to prevent overload from reaching that weak point in the first place.

System resilience

Real resilience during a surge usually comes from all three layers working together: caching absorbs repeated read load, rate limiting caps how much any single source can demand, and observability (next sections) lets engineers see and respond to what’s actually happening in real time.

7. Logging

What logging is

Logging is recording discrete events that happen within a system — a request received, an error thrown, a job completed — typically as timestamped text or structured records.

Why logs matter

Logs are often the first and most detailed source of truth when investigating exactly what happened during an incident — what request came in, what error occurred, in what order.

Types of logs

  • Application logs — events from your own code (errors, key actions)
  • Access logs — records of incoming requests (from a web server or load balancer)
  • System logs — infrastructure-level events (server restarts, resource limits)

Structured logging

Rather than free-form text (“User login failed for bob”), structured logging records events as consistent, queryable fields ({event: "login_failed", user: "bob", reason: "invalid_password", timestamp: ...}) — making logs far easier to search, filter, and aggregate at scale.

Common logging practices

Log meaningful events without excessive noise, include enough context (request IDs, user IDs) to trace a single request across a system, and avoid logging sensitive data (passwords, full payment details) directly.

8. Monitoring

What monitoring is

Monitoring is the continuous collection and tracking of quantitative measurements — metrics — about a system’s health and performance over time.

Metrics and measurements

Metrics are numeric values tracked over time: request rate, error rate, response latency, CPU usage, cache hit ratio — the vital signs of a running system.

System health tracking

Monitoring dashboards visualize these metrics so engineers can see, at a glance, whether the system is operating normally or drifting toward trouble.

Alerting and incident detection

Monitoring becomes actionable through alerting — automatically notifying engineers when a metric crosses a concerning threshold (error rate spikes, latency climbs, a service stops responding) — ideally before users notice the problem themselves.

Key backend metrics

  • Latency — how long requests take to complete
  • Error rate — the proportion of requests failing
  • Throughput — requests handled per unit time
  • Saturation — how close a resource (CPU, memory, connections) is to its limit

9. Observability

What observability means

Observability is the broader ability to understand a system’s internal state and behavior just by examining its external outputs — logs, metrics, and traces — especially for situations no one explicitly anticipated in advance.

Logs vs Monitoring vs Observability

  • Logging tells you what happened, event by event
  • Monitoring tells you how the system is behaving, quantitatively, over time
  • Observability is the broader capability these (plus tracing) combine to provide: the ability to ask new, previously unanticipated questions about system behavior and actually get answers

Why observability became important

Traditional monitoring was often built around a fixed, predefined set of dashboards for known failure modes. Modern distributed systems fail in unpredictable, novel ways — observability is about being equipped to investigate problems you didn’t specifically build a dashboard for in advance.

Understanding system behavior

Good observability lets an engineer go from “checkout is slow” to “the payment service’s third-party call is timing out for 12% of requests from this specific region” — without having anticipated that exact failure mode ahead of time.

Debugging distributed systems

In a system made of many independent services (echoing the microservices architecture from earlier articles in this series), a single user request might touch a dozen services — observability, especially distributed tracing, is what makes it possible to follow that request’s full journey and find exactly where it went wrong.

10. Introduction to OpenTelemetry

What OpenTelemetry is

OpenTelemetry is an open-source, vendor-neutral standard for collecting logs, metrics, and traces from applications — letting teams instrument their code once and send that data to whatever backend or dashboard tool they choose, rather than being locked into one vendor’s proprietary instrumentation.

Traces, Metrics, and Logs

OpenTelemetry standardizes all three “pillars” of observability under one framework:

  • Traces — the path a single request takes as it moves through multiple services
  • Metrics — the quantitative measurements covered in Section 8
  • Logs — the discrete event records covered in Section 7

Distributed tracing concepts

A trace is made up of spans — each span representing one unit of work (a database query, an API call to another service). Together, spans reconstruct the full path and timing of a single request across every service it touched, making it possible to pinpoint exactly which hop introduced a delay or an error.

Modern observability stacks

A typical modern setup instruments application code with OpenTelemetry, then exports that trace/metric/log data to a backend (open-source or commercial) for storage, visualization, and alerting — decoupling “how you collect data” from “where you analyze it.”

11. Scaling Architecture Together

Caching layer

Absorbs repeated, expensive reads before they ever reach the database — the first line of defense against redundant work under heavy load.

Rate limiting layer

Sits in front of the system, capping how much demand any single source can place on it — protecting capacity for legitimate traffic even during an abuse attempt or accidental overload.

Monitoring layer

Continuously tracks the vital signs of every other layer — cache hit ratio, rate-limit rejection rate, database latency — surfacing problems through alerts before they become outages.

Observability layer

Provides the deeper investigative capability to understand why a metric moved, tracing a single problematic request across caching, rate limiting, application logic, and the database to find the actual root cause.

Building production-grade systems

None of these four layers is optional at real scale — they’re complementary, each covering a different failure mode: caching handles load, rate limiting handles abuse and spikes, and monitoring/observability together handle visibility into how well the first two are actually working, especially in the moments that matter most.

Final Takeaway

A system that survives a flash sale, a viral moment, or a ticket-booking rush isn’t lucky — it’s built with three deliberate layers working together. Caching means the system doesn’t waste capacity redoing the same expensive work for every user. Rate limiting means no single source of demand, legitimate or malicious, can consume more than its fair share. And observability — logs, metrics, and traces, increasingly standardized through tools like OpenTelemetry — means that when something does go wrong (and eventually, something will), engineers can actually see it happening and understand why, instead of debugging blind. Scalability isn’t a single feature you add — it’s this combination, built in from the start.

Frequently Asked Questions

Should I add caching everywhere in my application by default?

> No — caching is most valuable for data that’s read often and doesn’t need to be perfectly real-time. Applying it everywhere without considering invalidation and freshness tradeoffs can introduce stale-data bugs that are harder to fix than the performance problem it was meant to solve.

Which rate limiting algorithm should I use?

> It depends on the traffic pattern you need to handle. Token bucket is a common, flexible default that allows reasonable bursts while enforcing a steady average rate; leaky bucket suits cases where you specifically need a perfectly smooth, steady outgoing rate regardless of burstiness.

Is observability just a more expensive version of monitoring?

> Not exactly — monitoring is generally built around predefined dashboards for known concerns, while observability is about having enough underlying data (logs, metrics, and traces together) to investigate problems you didn’t specifically anticipate in advance. They’re complementary, not competing.

Do small applications need OpenTelemetry and distributed tracing?

> Usually not at first. Distributed tracing earns its value once a request genuinely spans multiple independent services — for a small, single-service application, solid logging and basic metrics are often sufficient until that complexity actually arrives.

Originally published by Mr Madhukar

Read the complete article on Medium with full formatting & reader responses.