Click Below to Get the Code

Browse, clone, and build from real-world templates powered by Harper.
Blog
GitHub Logo

How to Reduce API Latency Under Load Without Adding Another Cache Tier

Reduce API latency under load with a caching alternative to Redis: an in-process cache that cut response time 100x without adding a new tier.
Cache
Blog
Cache

How to Reduce API Latency Under Load Without Adding Another Cache Tier

Austin Akers
Head of Developer Relations
at Harper
August 7, 2026
Austin Akers
Head of Developer Relations
at Harper
August 7, 2026
Austin Akers
Head of Developer Relations
at Harper
August 7, 2026
August 7, 2026
Reduce API latency under load with a caching alternative to Redis: an in-process cache that cut response time 100x without adding a new tier.
Austin Akers
Head of Developer Relations

An API can look healthy until request volume rises, the database becomes the bottleneck, and latency starts to spike. Queries that were inexpensive under normal traffic begin piling up, connection pools fill, and every request is still making a trip back to the same database. If the system is also handling frequent writes, the database has even more work to absorb.

Most experienced architecture teams already know the usual responses. Scale the database vertically. Add read replicas. Put Redis or another caching layer between the application and the database. These are all reasonable options, and for many workloads they are exactly the right ones.

But caching introduces another architectural question.

Does solving database load require adding another distributed system to the request path?

The usual cache architecture solves a real problem

A shared cache can dramatically reduce database reads. The application checks the cache first, falls back to the database on a miss, and stores the result so the next request does not repeat the query. It is a proven pattern, particularly when several application instances need access to the same cached state.

The trade-off is that the application now depends on another service. There is another network hop, another connection to manage, another system to scale and monitor, and another consistency relationship between the cache and the database.

Those costs may be justified. The point is that they should be justified by the workload rather than assumed to be necessary.

What if the application could remove repeated database work without adding a separate cache tier at all?

Testing another architecture with a search workload

We built a "House Listings Reference Application" on Harper to explore that question. link

The workload is intentionally familiar: structured listing data, multi-field filtering, sorting, pagination, repeated searches, and associated images. It looks a lot like a simplified product catalog, marketplace, or other read-heavy API.

House listing architecture with Harper

The architectural difference is that Harper runs the data, application logic, and result cache within the same runtime. The Listing schema defines the structured data and its API surface, while a custom SearchListings resource handles the application-specific search behavior.

On a cache miss, SearchListings scans the listing records, applies the requested filters, sorts the matches, selects the page, and caches the result. The response reports how much work occurred, including rows scanned, cache status, filtering time, and total server-side execution time.

The full scan is deliberate. This is not meant to demonstrate large-scale indexed search. It gives us a controlled amount of work so we can clearly see what happens when subsequent requests no longer have to perform it.

A cache hit removes the expensive path

The cache is a worker-local in-memory map with a 30-second TTL. Because it lives inside the runtime handling the request, serving a cached result does not require contacting another service.

Each query is normalized into a cache key that includes the filters, sorting, limit, and pagination offset. That allows repeated requests to reuse a result without mixing different queries or pages.

In this reference workload, an uncached search took roughly 5–6 milliseconds of server time. The same query served from the in-process cache took roughly 0.03–0.05 milliseconds.

The 100x difference is interesting, but the mechanism matters more. Harper did not make the database operation 100x faster. On the cached request, that work was no longer in the request path.

That becomes more meaningful as traffic increases. Under concurrent cold-cache traffic in the test, p95 server-side latency rose to roughly 100–130 milliseconds. With the relevant queries cached, the warm path remained inexpensive server-side.

The architectural question is bigger than caching

The same application stores roughly 42 MB of listing images as Harper blobs, separate from the records being searched. Search requests operate on less than 2 MB of structured data and only retrieve image bytes when the browser requests a specific photo.

That points to the broader thesis. The database, application logic, cache, API resources, and blob handling can operate as parts of the same application platform without forcing every workload through the same execution path.

A distributed cache still makes sense when cached state must be shared across independent services or instances, when it needs to scale separately, or when the application requires coordinated invalidation. This demo's cache is local to a worker, expires after 30 seconds, and does not implement mutation-driven invalidation.

But if database load is driving latency and caching is already on the table, there is another question worth asking before adding another tier:

Can the architecture remove the database work and the network boundary at the same time?

That is the alternative this application is designed to make concrete.

An API can look healthy until request volume rises, the database becomes the bottleneck, and latency starts to spike. Queries that were inexpensive under normal traffic begin piling up, connection pools fill, and every request is still making a trip back to the same database. If the system is also handling frequent writes, the database has even more work to absorb.

Most experienced architecture teams already know the usual responses. Scale the database vertically. Add read replicas. Put Redis or another caching layer between the application and the database. These are all reasonable options, and for many workloads they are exactly the right ones.

But caching introduces another architectural question.

Does solving database load require adding another distributed system to the request path?

The usual cache architecture solves a real problem

A shared cache can dramatically reduce database reads. The application checks the cache first, falls back to the database on a miss, and stores the result so the next request does not repeat the query. It is a proven pattern, particularly when several application instances need access to the same cached state.

The trade-off is that the application now depends on another service. There is another network hop, another connection to manage, another system to scale and monitor, and another consistency relationship between the cache and the database.

Those costs may be justified. The point is that they should be justified by the workload rather than assumed to be necessary.

What if the application could remove repeated database work without adding a separate cache tier at all?

Testing another architecture with a search workload

We built a "House Listings Reference Application" on Harper to explore that question. link

The workload is intentionally familiar: structured listing data, multi-field filtering, sorting, pagination, repeated searches, and associated images. It looks a lot like a simplified product catalog, marketplace, or other read-heavy API.

House listing architecture with Harper

The architectural difference is that Harper runs the data, application logic, and result cache within the same runtime. The Listing schema defines the structured data and its API surface, while a custom SearchListings resource handles the application-specific search behavior.

On a cache miss, SearchListings scans the listing records, applies the requested filters, sorts the matches, selects the page, and caches the result. The response reports how much work occurred, including rows scanned, cache status, filtering time, and total server-side execution time.

The full scan is deliberate. This is not meant to demonstrate large-scale indexed search. It gives us a controlled amount of work so we can clearly see what happens when subsequent requests no longer have to perform it.

A cache hit removes the expensive path

The cache is a worker-local in-memory map with a 30-second TTL. Because it lives inside the runtime handling the request, serving a cached result does not require contacting another service.

Each query is normalized into a cache key that includes the filters, sorting, limit, and pagination offset. That allows repeated requests to reuse a result without mixing different queries or pages.

In this reference workload, an uncached search took roughly 5–6 milliseconds of server time. The same query served from the in-process cache took roughly 0.03–0.05 milliseconds.

The 100x difference is interesting, but the mechanism matters more. Harper did not make the database operation 100x faster. On the cached request, that work was no longer in the request path.

That becomes more meaningful as traffic increases. Under concurrent cold-cache traffic in the test, p95 server-side latency rose to roughly 100–130 milliseconds. With the relevant queries cached, the warm path remained inexpensive server-side.

The architectural question is bigger than caching

The same application stores roughly 42 MB of listing images as Harper blobs, separate from the records being searched. Search requests operate on less than 2 MB of structured data and only retrieve image bytes when the browser requests a specific photo.

That points to the broader thesis. The database, application logic, cache, API resources, and blob handling can operate as parts of the same application platform without forcing every workload through the same execution path.

A distributed cache still makes sense when cached state must be shared across independent services or instances, when it needs to scale separately, or when the application requires coordinated invalidation. This demo's cache is local to a worker, expires after 30 seconds, and does not implement mutation-driven invalidation.

But if database load is driving latency and caching is already on the table, there is another question worth asking before adding another tier:

Can the architecture remove the database work and the network boundary at the same time?

That is the alternative this application is designed to make concrete.

Reduce API latency under load with a caching alternative to Redis: an in-process cache that cut response time 100x without adding a new tier.

Download

White arrow pointing right
Reduce API latency under load with a caching alternative to Redis: an in-process cache that cut response time 100x without adding a new tier.

Download

White arrow pointing right
Reduce API latency under load with a caching alternative to Redis: an in-process cache that cut response time 100x without adding a new tier.

Download

White arrow pointing right

Explore Recent Resources

News
GitHub Logo

Harper 5.2: More Throughput per Node, Fewer Systems Around It

Harper 5.2 helps architecture and platform leaders improve performance, control infrastructure costs, secure production workloads, and reduce operational complexity by bringing faster data access, agentic capabilities, scheduling, backup, routing, and protection into one unified runtime.
Product Update
News
Harper 5.2 helps architecture and platform leaders improve performance, control infrastructure costs, secure production workloads, and reduce operational complexity by bringing faster data access, agentic capabilities, scheduling, backup, routing, and protection into one unified runtime.
Person with short dark hair and moustache, wearing a colorful plaid shirt, smiling outdoors in a forested mountain landscape.
Aleks Haugom
Senior Manager of GTM
News

Harper 5.2: More Throughput per Node, Fewer Systems Around It

Harper 5.2 helps architecture and platform leaders improve performance, control infrastructure costs, secure production workloads, and reduce operational complexity by bringing faster data access, agentic capabilities, scheduling, backup, routing, and protection into one unified runtime.
Aleks Haugom
Aug 2026
News

Harper 5.2: More Throughput per Node, Fewer Systems Around It

Harper 5.2 helps architecture and platform leaders improve performance, control infrastructure costs, secure production workloads, and reduce operational complexity by bringing faster data access, agentic capabilities, scheduling, backup, routing, and protection into one unified runtime.
Aleks Haugom
News

Harper 5.2: More Throughput per Node, Fewer Systems Around It

Harper 5.2 helps architecture and platform leaders improve performance, control infrastructure costs, secure production workloads, and reduce operational complexity by bringing faster data access, agentic capabilities, scheduling, backup, routing, and protection into one unified runtime.
Aleks Haugom
Blog
GitHub Logo

5 Architectures for Web Personalization

Personalization is a data-delivery problem. Every architectural choice reduces to two distances: compute to user, and compute to fresh data. This piece maps five real architectures against both axes, scored on a concrete retailer workload where stale or slow data breaks the business.
Blog
Personalization is a data-delivery problem. Every architectural choice reduces to two distances: compute to user, and compute to fresh data. This piece maps five real architectures against both axes, scored on a concrete retailer workload where stale or slow data breaks the business.
Person with short dark hair and moustache, wearing a colorful plaid shirt, smiling outdoors in a forested mountain landscape.
Aleks Haugom
Senior Manager of GTM
Blog

5 Architectures for Web Personalization

Personalization is a data-delivery problem. Every architectural choice reduces to two distances: compute to user, and compute to fresh data. This piece maps five real architectures against both axes, scored on a concrete retailer workload where stale or slow data breaks the business.
Aleks Haugom
Jul 2026
Blog

5 Architectures for Web Personalization

Personalization is a data-delivery problem. Every architectural choice reduces to two distances: compute to user, and compute to fresh data. This piece maps five real architectures against both axes, scored on a concrete retailer workload where stale or slow data breaks the business.
Aleks Haugom
Blog

5 Architectures for Web Personalization

Personalization is a data-delivery problem. Every architectural choice reduces to two distances: compute to user, and compute to fresh data. This piece maps five real architectures against both axes, scored on a concrete retailer workload where stale or slow data breaks the business.
Aleks Haugom
Blog
GitHub Logo

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Select*
Blog
AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
A smiling man with a beard and salt-and-pepper hair stands outdoors with arms crossed, wearing a white button-down shirt.
Stephen Goldberg
CEO & Co-Founder
Blog

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Stephen Goldberg
Jun 2026
Blog

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Stephen Goldberg
Blog

Agentic Engineering Needs an Opinion: Why Scale Starts with Architecture

AI coding works in a sandbox because the environment is trivially narrow. Real systems have history, constraints, and blast radius. Coding agents make sound decisions only when the architecture is explicit and shared. Opinion isn't a constraint on agentic engineering, it's what makes it possible at scale.
Stephen Goldberg