Why a web application slows down under load
Under load, an application almost never hits a CPU bottleneck — it hits the database. Every page request pulls dozens of similar queries: catalog, reference data, user permissions, counters. While there are hundreds of requests per minute, this goes unnoticed.
At peak, the same data is read thousands of times, database connections run out, queries queue up — and response time grows not linearly but collapses: it was 200 ms, now it's several seconds.
Then the cascade kicks in. A slow response holds a worker thread, threads run out, new requests pile up, and clients start retrying requests — and the load on the database doubles out of nowhere.
This is exactly how a peak slowdown turns into an outage, even though the code and hardware haven't changed. Data caching removes this bottleneck: the result of a heavy query is computed once and served from memory on all subsequent requests.
Configuring Redis here is not "install and enable" but determining what exactly to cache, for how long, and how to invalidate stale data. Too short a TTL — the database isn't offloaded, too long — the user sees stale data.
A separate topic is behavior when the cache is unavailable: the application must degrade to a database query rather than fail entirely.
Let's break down how this is addressed: what data goes into the cache, how TTLs and invalidation are set, and how to verify that response time has truly stopped depending on load.
What proper Redis configuration in an application delivers
Configuring Redis starts not with installation, but with answering the question of what data can even be kept in memory and for how long.
We classify the application's requests by frequency and cost: reference data and the results of heavy queries are cached with a TTL, sessions and shopping carts — with explicit invalidation on write, counters and limits — through atomic operations.
Then we select an eviction policy to match the load profile: for a hot set of reference data, that means retaining frequently requested keys; for a stream of one-off values, eviction by time-to-live.
Without this classification, the cache either quickly fills up with junk or serves stale data, and trust in it disappears.
The effect is measurable at every layer. Response time for typical scenarios drops severalfold: a query that previously went to the database and was assembled from several requests is served from memory in a fraction of that time.
The database is offloaded — the share of reads reaching it shrinks, connections and disk I/O are freed up, and heavy reports stop competing with user requests for resources.
Resilience to spikes is a separate task. We configure behavior on memory overflow so that the application degrades predictably rather than crashing: some keys are evicted, critical operations keep working.
For sessions and queues we enable persistence to disk and replication, so that restarting a node doesn't mean logging out all users.
Separately, we close off the class of failures that most often hits a cache: a simultaneous miss on the same key, when hundreds of requests all go to the database at once and bring it down. Locking during recomputation and staggering key lifetimes eliminate this scenario.
All of this delivers predictable behavior under load: the application responds quickly, the database isn't overloaded, and a traffic spike doesn't turn into an outage.
Caching modes: comparison of approaches
The choice of caching mode determines what happens on a miss, on a write, and when a cache node fails. Below are four approaches that are realistically chosen between when configuring Redis: they differ in who is responsible for consistency and what price the application pays.
| Mode | When it fits | Limitations |
|---|---|---|
| Cache-aside | Read with infrequent writes: catalogs, reference data, profiles | The application itself writes to the cache and invalidates; brief desynchronization is possible |
| Write-through | Data must be in the cache immediately after a write | Every write goes through the cache — write latency increases |
| Write-behind | High write frequency, delay in flushing to storage is acceptable | Risk of losing the latest writes if a node fails |
| TTL policies | Reference and computed data with a natural lifespan | Without explicit invalidation, a stale value lives until the TTL expires |
What TTL and invalidation solve
TTL is insurance, not a strategy: it limits the lifetime of a stale value but does not replace explicit invalidation on update.
In practice, a short TTL for hot data is combined with targeted key deletion on a write event — this way a miss happens rarely, and desynchronization does not accumulate.
How we choose a mode
We take cache-aside by default: it does not change the write logic to the primary storage and survives a cache restart without data loss. We enable write-through and write-behind where writes and reads go through a single node and latency is predictable.
We fix the decision in the configuration and describe it in the documentation so that failure behavior is explicit.
Stages of implementing caching in a web application
Implementing caching is not "install Redis and turn it on," but a sequence of steps where each subsequent one builds on the result of the previous.
If you skip the audit and jump straight to configuration, the cache starts serving stale data, and the team chases bugs that cannot be reproduced. Below is the process by which we guide a project from the first measurement to handover into operation.
- Load and growth-point audit. We capture a request profile: which endpoints are called most often, how long each one takes, where the bottleneck is — in the database, in an external service, or in computations. The output is a list of caching candidates sorted by impact, not by intuition.
- Key schema and expiration policy. We design the namespace: prefixes by entity, versioning when the format changes, a unified style for key formation. For each data type we define the time to live and the invalidation rule — by write event or by timer.
- Storage configuration. We select the eviction mode to match the data profile, limit memory consumption, enable persistence to disk where cache loss is critical, and separate environments so that test data does not affect the production circuit.
- Integration into the application. We connect the cache layer so that when the storage is unavailable, the application continues to work directly with the data source. This is a key requirement: the cache speeds things up, but must not become a single point of failure.
- Testing under load. We compare response time and the number of database calls before and after, and check behavior during warm-up, during a flush, and during a sharp traffic spike. Separately, we look at the hit ratio — if it is low, the key schema or the time to live was chosen incorrectly.
- Observability. We set up metrics: hits and misses, the amount of occupied memory, eviction, storage response time. Without this, degradation is noticed only through user complaints.
- Handover and documentation. We give the team the key schema, expiration policies, instructions for changing the configuration, and an analysis of typical failures. The client's engineers must be able to change the time to live and add new keys without our involvement.
Each stage ends with an artifact that can be verified: a load profile, a key map, a configuration, a measurement report. This order allows you to stop at any step and see what has already produced an effect and what has not yet.
What the client receives
After Redis is configured, caching stops being "magic behind the scenes": the client receives a complete set of artifacts that make the solution operable, transferable to another team, and developable without us.
- Configurations: working Redis files with memory parameters, key eviction policy, disk persistence mode, and connection limits — with comments explaining what is set and why.
- Caching schemas: a map of what data is cached, by which keys, for how long, and how it is invalidated when the source is updated — so there are no "eternal" stale values.
- Access policies: permission separation, network restrictions, protection against unauthorized connections, and access rotation rules.
- Invalidation and warm-up regulations: what to do on release, how to warm up the cache after a restart, how to avoid a request avalanche to the database.
- Monitoring and alerts: a set of metrics (hits/misses, memory usage, key eviction, latency) and thresholds at which a notification is sent.
- Operations manual: typical operations, degradation diagnostics, the procedure for scheduled restarts and rollback.
- Documentation: a description of the cache architecture, integration points with the application, and those responsible for changes.
Each artifact is not a formality but a working tool: with it, a new engineer is onboarded into the project in days rather than weeks, and incidents are handled according to a clear regulation rather than from one person's memory.
Describe your task — we will come back with an estimate and a plan of exactly what will be included in the set for your project.
Case Study: Speeding Up an Online Store After Configuring Redis
An online store on a dedicated server: a catalog with tens of thousands of items, peak load during evening hours and sales.
Every listing and product page view went to the database — as traffic grew, response time increased non-linearly, and at peaks, connection queues and timeout errors appeared.
There was no cache: the same catalog queries and "similar products" blocks were executed anew for each visitor.
What It Was Like Before Configuration
Measurements under load showed: the median catalog response time held around 800–900 ms, and at peaks it went over 2 seconds. The database handled almost all read traffic, some queries were waiting, and during activity spikes, the cart and checkout began responding with delays. There were few failures, but they were the ones hurting conversion — the buyer left a slow page.
What Was Changed
Redis was configured as a caching layer between the application and the database: hot catalog queries, the category tree, recommendation blocks, and results of heavy aggregates were moved into it.
For each data type, its own time-to-live was set — static reference data lives longer, prices and stock update more frequently. Cache invalidation by event was added: a change to a product or stock invalidates related keys, so the buyer doesn't see a stale price. The cache was warmed up after deployment so the first visitor wouldn't wait for a cold start.
What It Became
The median catalog response time dropped to 120–180 ms — five times faster. The read load on the database fell by roughly 70%, connection queues at peaks disappeared, and timeout errors during checkout no longer reproduce.
With the same hardware, the store handles several times more traffic, and behavior under load has become predictable: response time grows smoothly rather than falling off a cliff.
This is the practical result of configuring Redis — not "cache for cache's sake," but controlled degradation instead of failure.
How is Redis different from other caching solutions?
Redis occupies a niche that neither an application's built-in cache, nor memcached, nor a CDN fills.
A built-in cache lives in the memory of a single process: it is reset on restart or deploy, and with multiple application instances each one keeps its own copy of the data — desynchronization and extra database queries.
Redis moves the cache into a separate layer shared by all instances, and it survives an application restart if disk persistence is configured.
Memcached solves the same task of a "fast key-value store," but remains simple: strings only, no data structures, no replication, and no persistence on failure.
Redis provides lists, sets, sorted sets, and hashes — they are used to build queues, counters, rate limits, and locks without separate infrastructure.
That is precisely why, when choosing between them for a web application, Redis is more often picked: it covers caching and related tasks with a single component.
A CDN is not a competitor but a different level. It caches static assets and responses at the network edge, closer to the user, and offloads servers for images, scripts, and styles.
But dynamic data specific to a user — a cart, a session, database query results — is not cached by a CDN. Redis works on the application side and complements a CDN rather than replacing it.
In practice, these solutions are combined: a CDN serves static assets, Redis holds hot data and sessions, and the built-in cache remains for small things like reference data that is not a problem to rebuild.
We analyze the application's load profile and determine what to cache and at which level — this determines how many requests will even reach the database.
Technical doubts and answers
Setting up Redis is not a "install the package and forget it" task: the cache lives between the application and the database, and both the response speed and the system's behavior during a failure depend on how it is designed.
Below is an analysis of the technical doubts that most often arise before the start: how security is handled, what happens when a node goes down, and how labor-intensive integration into an existing project is.
Redis stores data in memory — does that mean the cache is lost on restart and the application crashes?
Losing the cache should not break the logic: the application must work with an empty cache, just more slowly — this is a basic requirement for a caching scheme, and we account for it at the design stage.
For structures critical to preservation, we enable an operation log and periodic snapshots to disk, so that after a restart the data is restored. Separately, we configure the key eviction policy so that memory does not grow uncontrollably.
How secure is this — the cache is accessible over the network, after all?
We isolate the instance in a private network, do not expose it externally, and restrict access by the application's address list. Authentication, traffic encryption, and disabling dangerous administrative commands that allow arbitrary code execution on the server are mandatory. We store access secrets not in code, but in a secure configuration store.
What happens if a node fails — will we lose availability?
For fault tolerance, we deploy a scheme with replication and automatic failover to a standby node: if the primary goes down, traffic goes to the replica without manual intervention.
The application's client library is configured for retries and correct bypassing of an unavailable node. Such a setup survives planned maintenance and hardware failure.
Is it difficult to embed caching into an already running project?
We start with profiling: we look at which database queries are executed most often and produce the greatest latency — we cache exactly those, not everything indiscriminately. We tie invalidation to data change events so that the user does not receive stale values.
The rollout is phased, without stopping the service, with the ability to roll back an individual section.
If your case is not on the list — describe the task, and we will come back with an estimate and a plan.
Let's discuss your caching task
A discussion of a caching task starts not with choosing a tool, but with analyzing the load profile: which requests dominate, where the database is currently hitting a wall, and which data can be kept in memory without risking integrity.
Redis fills this niche, but the result depends on how precisely we match your scenarios — from catalog caching to sessions and queues. Below is what the start looks like, so you understand what you get at each step.
How the start goes
- Task and metrics analysis. We look at the current behavior of the application: response time, the share of repeated requests, load peaks. We pin down exactly what hurts — slow pages, database overload, or instability under peak load.
- Agreeing on the caching plan. We determine what we cache, for how long, and by what criterion we invalidate it. We define the expiration policy and miss behavior so that data does not diverge from the source.
- Designing the key schema. We think through the key structure, separation by data types and namespaces — this makes it easier to manage invalidation and avoid conflicts between different parts of the application.
- Setting up the environment. We deploy Redis, configure the persistence mode, memory limits, and eviction rules. Separately, we lock down access and isolation from the external network.
- Integrating into the application. We connect the cache layer, add warm-up and invalidation on data changes. We verify that when the cache is unavailable, the application runs normally rather than crashing.
- Testing under load. We run scenarios with a real request profile and look at the hit ratio and response time before and after. We make sure the gain is stable rather than one-off.
- Handover and support. We hand over the configurations, key schema, and operating instructions. After that — monitoring and targeted fine-tuning as the load grows.
To come back with an estimate and a plan, describe the task: what stack you use, where the bottleneck is now, and what result you need. That is enough to propose a concrete caching scheme and a sequence of work.







