Daniel Murillo
Index

Architecture · October 24, 2026 · Daniel Murillo

Cache and compression: optimize before you scale

Cache avoids repeated work; compression reduces bytes. How to apply them safely in REST, GraphQL, gRPC, and real time.

Cache avoids repeated work; compression reduces the data that is transmitted. They are complementary strategies, but each has costs and limits.

Introduction

When an API is slow, the first reaction may be to change protocol, grow infrastructure, or split the backend into more services. However, an important part of performance can improve by reusing results, reducing unnecessary bytes, and delivering resources appropriately.

This article explores cache and compression as cross-cutting strategies that can benefit REST APIs, GraphQL, gRPC, and real-time applications, even though they are not configured the same way in all of them.

1. Cache: do not repeat the work

A cache stores a result to reuse it later. It can reduce database queries, processing, and transfer.

Common layers:

  • Local client cache.
  • Browser HTTP cache.
  • CDN or proxy.
  • Application cache, for example Redis.
  • Internal caches in the database engine.

Each layer has a different purpose. It is not enough to turn a cache on: you must define what is stored, for how long, who may reuse it, and how it is invalidated.

2. HTTP cache

For a cacheable response, the server can send:

Cache-Control: public, max-age=60
ETag: "product-123-v4"
Vary: Accept-Encoding

Cache-Control defines storage policies. ETag identifies a version of the resource and enables conditional validation. Vary indicates that certain request headers affect the selected representation.

With If-None-Match, the client can ask whether the resource changed. If it did not, the server responds 304 Not Modified and avoids retransmitting the body.

Private or personalized responses need appropriate policies. A user response must never be shared on a CDN if the configuration does not guarantee that the cache is correctly isolated.

3. Application cache

Redis or an in-memory cache can store results of expensive operations. They are useful when multiple requests reuse data and the cost of recomputing them is high.

However, they also introduce problems:

  • Data invalidation.
  • Expiration and freshness.
  • Poorly designed keys.
  • Memory consumption.
  • Inconsistencies across instances.
  • Effects of a cold cache.

The cache should answer an observed need. Caching indiscriminately can add complexity without reducing real cost.

4. Gzip, Brotli, and Zstandard

Compression reduces the size of transmitted content, but consumes CPU to compress and decompress.

Gzip

It is widely compatible and works well with JSON, HTML, CSS, and JavaScript. For dynamic responses, an intermediate level such as 5 or 6 is usually a reasonable starting point, but it should be measured.

Brotli

It can produce smaller textual files than Gzip in many scenarios. It is especially useful for static web assets and can serve dynamic responses if the infrastructure supports it. High levels can cost more CPU.

Zstandard

It offers levels and a balance between speed and compression that can be useful in internal services and data processing. Support for HTTP compression must be verified across all components; you should not assume every browser and proxy supports it.

5. Choosing compression by workload

There is no universally optimal level.

  • Small responses: the savings may be insignificant and not worth the compression cost.
  • Medium responses: moderate Gzip is usually a practical starting point.
  • Large textual responses: Brotli or Gzip can noticeably reduce transfer.
  • Saturated CPU: aggressive compression can increase latency.
  • Slow or costly network: saving bytes can provide more value.
  • Already compressed content: recompressing JPEG, WebP, AVIF, video, or audio normally helps little.

Measure original size, compressed size, compression time, decompression time, and CPU under load.

6. Static files versus dynamic responses

Static files are generated during the build and usually stay the same until a new release. That is why they can be precompressed:

assets/
├── app.js
├── app.js.gz
└── app.js.br

The server or CDN selects the variant compatible with Accept-Encoding. That way, you do not need to compress the file on every request.

Content-hashed resources can have a long policy:

Cache-Control: public, max-age=31536000, immutable

This is only appropriate if the name changes when the content changes.

Dynamic responses, by contrast, may depend on the user, permissions, and current data. Compressing them on every request consumes CPU and must be configured with appropriate thresholds and levels.

7. How does this fit each communication model?

REST: can leverage HTTP cache naturally for compatible resources and operations, plus response compression.

GraphQL: can use entity cache, result cache, persisted queries, and HTTP cache on compatible operations. The shape of the query and the variables influence the identity of the result.

gRPC: can reuse results in the application and compress messages when supported and beneficial. Conventional HTTP cache does not apply automatically as it does to a REST GET.

WebSockets and SSE: the strategy usually centers on application state, snapshots, events, and recovery after reconnections. Compression depends on the transport and negotiated extensions.

8. Compression and security

Compressing sensitive data together with attacker-controlled information can create risks in certain contexts, especially when compressed sizes leak information. You must evaluate the security design, not enable compression indiscriminately.

It is also worth avoiding double compression in proxies and CDNs, and correctly configuring content negotiation.

9. A practical optimization process

  1. Measure response size and frequency.
  2. Identify repeated data and expensive operations.
  3. Define which responses can be stored safely.
  4. Establish expiration and invalidation.
  5. Separate static files from dynamic responses.
  6. Precompress static assets.
  7. Test Gzip and Brotli with representative sizes.
  8. Measure CPU, latency, and bytes transferred.
  9. Validate behavior under real concurrency.
  10. Review consistency and security of the caches.

Conclusion

Cache and compression attack different problems. The first avoids repeated work; the second reduces transmitted bytes. Combined correctly, they can improve performance without changing the communication protocol.

But neither should be applied blindly. An incorrect cache can deliver stale or private data; excessive compression can consume CPU and increase latency.

Optimize what actually costs: reuse results when it is safe, and compress when the transfer savings outweigh the extra processing.

In this series

  1. REST: how to design and optimize an API
  2. REST vs GraphQL
  3. gRPC vs REST
  4. WebSockets, SSE, and Webhooks
  5. Cache and compression (this note)
  6. Benchmarks and architecture decisions

References