2026-08-24 16:05:52.845346+08 / restful-api

Performance

Guidelines for compression, partial responses, ETags, pagination, Prefer, and HTTP caching in RESTful APIs.

Restful ApiApi DesignOpenapi

14. Performance

Performance is an important aspect of RESTful API design, especially for APIs that return large payloads or operate under high traffic.

API designers should minimize:

  • Network bandwidth usage
  • Response latency
  • Unnecessary data transfer
  • Repeated retrieval of unchanged resources
  • Unnecessary server-side processing

Common techniques include:

  • Response compression
  • Partial responses and field filtering
  • Conditional requests using ETag
  • Appropriate HTTP caching
  • Pagination
  • Minimal responses
  • Asynchronous processing when appropriate

14.1 Minimize Bandwidth Usage and Latency

APIs SHOULD provide mechanisms to minimize bandwidth usage and latency when the expected payload size or request volume makes this beneficial.

This is particularly important for:

  • Large collections
  • Large resource representations
  • High-traffic APIs
  • Mobile clients
  • Clients operating over slow or high-latency networks

Common optimization techniques include:

  1. Compress request and response payloads where appropriate.
  2. Allow clients to request only required resource fields.
  3. Use ETag and conditional requests to avoid transferring unchanged resources.
  4. Use the Prefer header when clients can accept reduced or asynchronous processing.
  5. Use pagination for large collections.
  6. Cache resources that are stable or rarely modified.

For example:

http
GET /users/123 HTTP/1.1
Accept: application/json
Accept-Encoding: gzip

A server may respond with:

http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip

Performance optimizations should be selected based on actual API characteristics rather than applied blindly. For example, compression may provide little benefit for very small responses while still consuming CPU resources.


14.2 Use Response Compression

APIs SHOULD support payload compression when responses are large enough for compression to provide a meaningful reduction in transferred data.

gzip is a commonly supported HTTP compression format.

Clients indicate supported compression algorithms using the Accept-Encoding request header:

http
GET /users HTTP/1.1
Host: api.example.org
Accept-Encoding: gzip

If the server uses gzip compression, it should indicate this using the Content-Encoding response header:

http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip

The server should still support uncompressed responses.

For example, a client that does not send:

http
Accept-Encoding: gzip

should be able to receive the normal representation:

http
HTTP/1.1 200 OK
Content-Type: application/json

Compression may not be appropriate when:

  • Responses are very small.
  • Payloads are already compressed.
  • Compression CPU cost becomes significant.
  • Latency introduced by compression exceeds the network savings.

Compression behavior should therefore be based on payload characteristics and expected traffic.


14.3 Support Partial Responses When Appropriate

For resources with many fields or deeply nested structures, APIs SHOULD consider allowing clients to request only the fields they actually need.

This can significantly reduce:

  • Response size
  • Network bandwidth
  • JSON serialization cost
  • Client-side parsing cost

A fields query parameter may be used for this purpose.

Full Response

Example:

http
GET /users/123 HTTP/1.1

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "cddd5e44-dae0-11e5-8c01-63ed66ab2da5",
  "name": "John Doe",
  "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
  "birthday": "1984-09-13",
  "friends": [
    {
      "id": "1fb43648-dae1-11e5-aa01-1fbc3abb1cd0",
      "name": "Jane Doe",
      "address": "1600 Pennsylvania Avenue Northwest, Washington, DC, United States",
      "birthday": "1988-04-07"
    }
  ]
}

Partial Response

If the client only needs the user's name and the names of friends:

http
GET /users/123?fields=(name,friends(name)) HTTP/1.1

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json

{
  "name": "John Doe",
  "friends": [
    {
      "name": "Jane Doe"
    }
  ]
}

The fields parameter determines which properties are returned in the response.

For example:

text
fields=(name)

returns only:

json
{
  "name": "John Doe"
}

While:

text
fields=(name,friends(name))

returns the top-level name field and the friends collection containing only each friend's name.


14.3.1 Field Selection Syntax

A possible grammar for the fields parameter is:

text
<fields>            ::= [ <negation> ] <fields_struct>
<fields_struct>     ::= "(" <field_items> ")"
<field_items>       ::= <field> [ "," <field_items> ]
<field>             ::= <field_name> | <fields_substruct>
<fields_substruct>  ::= <field_name> <fields_struct>
<field_name>        ::= <dash_letter_digit> [ <field_name> ]
<dash_letter_digit> ::= <dash> | <letter> | <digit>
<dash>              ::= "-" | "_"
<letter>            ::= "A" | ... | "Z" | "a" | ... | "z"
<digit>             ::= "0" | ... | "9"
<negation>          ::= "!"

Examples:

text
fields=(name)
text
fields=(name,email)
text
fields=(name,friends(name))
text
fields=(id,name,address)

If negation semantics are supported, they must be clearly documented.


14.3.2 Do Not Define a Surprising Default Field Filter

The fields query parameter SHOULD NOT have a hidden or surprising default value.

For example, an API should avoid defining:

text
fields=(id,name)

as an implicit default while silently omitting other properties.

Without an explicit fields parameter, clients should normally receive the documented standard representation of the resource.

This follows the principle of least astonishment: clients should not unexpectedly receive incomplete resource representations.


14.3.3 Document Partial Response Behavior

OpenAPI cannot fully express arbitrary response schemas resulting from dynamic field selection.

Therefore, APIs supporting field filtering should clearly document the behavior of the fields parameter.

Example:

yaml
parameters:
  - name: fields
    in: query
    required: false
    description: >
      Selects the fields returned in the response.
      Nested fields may be selected using expressions such as
      `(name,friends(name))`.
    schema:
      type: string
    example: "(name,friends(name))"

The full response schema should still describe the complete resource representation.


14.4 Use Conditional Requests to Avoid Unnecessary Transfers

APIs may use entity tags to identify specific versions of resources.

A server can return an ETag header:

http
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "7e2f31a"

{
  "id": "123",
  "name": "John Doe"
}

The client can store this entity tag and use it in later requests:

http
GET /users/123 HTTP/1.1
If-None-Match: "7e2f31a"

If the resource has not changed, the server can respond:

http
HTTP/1.1 304 Not Modified
ETag: "7e2f31a"

The resource representation does not need to be transferred again.

This is particularly useful for:

  • Large resources
  • Frequently requested resources
  • Reference data
  • Master data
  • Resources that change infrequently

14.5 ETag and If-Match

ETag may also be used with If-Match to perform conditional updates.

For example, assume a client retrieved:

http
ETag: "version-42"

The client can later send:

http
PUT /users/123 HTTP/1.1
Content-Type: application/json
If-Match: "version-42"

{
  "name": "John Smith"
}

The server should only perform the operation if the current resource still matches the supplied entity tag.

This mechanism can help prevent clients from unintentionally overwriting changes made by another client.

For cache validation:

http
If-None-Match

is typically used.

For conditional modification:

http
If-Match

is typically used.


14.6 Use Pagination for Large Collections

Large collections SHOULD NOT normally be returned as a single unbounded response.

Instead, APIs should support pagination.

For example:

http
GET /users?limit=100

or:

http
GET /users?page=2&page-size=100

Depending on the API design, cursor-based pagination may also be used:

http
GET /users?limit=100&cursor=eyJpZCI6IjEyMyJ9

Pagination limits:

  • Response size
  • Memory usage
  • Serialization cost
  • Network bandwidth
  • Client processing cost

The selected pagination mechanism should remain consistent across the API.


14.7 Use the Prefer Header When Appropriate

The HTTP Prefer header can be used to communicate client processing preferences.

Two useful examples are:

http
Prefer: return=minimal

and:

http
Prefer: respond-async

Minimal Response

When the client does not require a complete resource representation after an operation:

http
PUT /users/123 HTTP/1.1
Prefer: return=minimal
Content-Type: application/json

{
  "name": "John Doe"
}

The server may avoid generating and transferring an unnecessary response representation.

Asynchronous Processing

For operations that may require significant processing time:

http
POST /reports HTTP/1.1
Prefer: respond-async
Content-Type: application/json

The server may process the request asynchronously when this behavior is supported.

The exact behavior of supported preferences must be documented by the API.


14.8 HTTP Caching

HTTP caching can significantly improve performance, but it introduces complexity related to:

  • Authorization
  • Data consistency
  • Cache invalidation
  • Resource updates
  • Multiple users
  • Shared versus private caches
  • Cache lifetime

Caching should therefore be introduced deliberately.

For APIs where caching is not explicitly required, a conservative default is recommended.

For example:

http
Cache-Control: no-cache, no-store, must-revalidate, max-age=0

In particular:

http
Cache-Control: no-store

prevents clients and intermediaries from storing the response.

This is a suitable default for APIs where responses should not be cached unless explicitly designed otherwise.


14.9 Document Cacheable Endpoints

If an API explicitly supports caching, its cache behavior MUST be documented.

This applies to cacheable:

  • GET
  • HEAD
  • POST

operations where caching has intentionally been enabled.

The documentation should describe relevant headers such as:

  • Cache-Control
  • Vary
  • ETag

The API should avoid using both Expires and Cache-Control to define overlapping cache lifetime semantics when Cache-Control already provides the required policy.


14.10 Cache-Control

Cache-Control defines how clients and intermediary caches are allowed to store and reuse responses.

Example:

http
Cache-Control: private, must-revalidate, max-age=300

This means:

  • private — the response is intended for a single user and must not be stored by a shared cache.
  • must-revalidate — stale responses must be revalidated before reuse.
  • max-age=300 — the response may be considered fresh for 300 seconds.

For authenticated user-specific APIs, private is generally safer than public when caching is explicitly enabled.

Example:

http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, must-revalidate, max-age=300
ETag: "123456"

14.11 Vary

The Vary response header identifies request headers that influence the selected representation.

For example:

http
Vary: Accept, Accept-Encoding

This informs caches that responses may differ depending on:

http
Accept

and:

http
Accept-Encoding

Consider:

http
GET /users/123 HTTP/1.1
Accept: application/json
Accept-Encoding: gzip

and:

http
GET /users/123 HTTP/1.1
Accept: application/json
Accept-Encoding: identity

Although both requests target the same URI, the resulting representations may differ because one response is compressed and the other is not.

The cache must therefore treat them as different variants.

Other headers may also need to be included in Vary, depending on the API.

For example:

http
Vary: Accept, Accept-Encoding, Accept-Language

The correct value depends on the characteristics of the endpoint.


14.12 OpenAPI Documentation for Cache Headers

Cache-related response headers can be documented as reusable OpenAPI components.

Example:

yaml
components:
  headers:
    Cache-Control:
      description: >
        Controls whether and for how long clients and intermediary caches
        may cache the response.
      schema:
        type: string
      example: "private, must-revalidate, max-age=300"

    Vary:
      description: >
        Identifies request headers that may influence the selected response
        representation and therefore must be considered when caching.
      schema:
        type: string
      example: "Accept, Accept-Encoding"

    ETag:
      description: >
        Identifies a particular version of the resource representation
        and may be used for conditional requests.
      schema:
        type: string
      example: "\"7e2f31a\""

The headers can then be referenced from individual operations:

yaml
paths:
  /users/{id}:
    get:
      responses:
        "200":
          description: User returned successfully
          headers:
            Cache-Control:
              $ref: "#/components/headers/Cache-Control"
            Vary:
              $ref: "#/components/headers/Vary"
            ETag:
              $ref: "#/components/headers/ETag"

14.13 Cache Validation

Clients should be able to efficiently determine whether a cached representation is still valid.

For example:

http
GET /users/123 HTTP/1.1
If-None-Match: "7e2f31a"

If the resource has not changed:

http
HTTP/1.1 304 Not Modified
ETag: "7e2f31a"

The response must not contain the full resource representation.

If the resource has changed:

http
HTTP/1.1 200 OK
Content-Type: application/json
ETag: "9a82b12"

{
  "id": "123",
  "name": "John Smith"
}

This allows the client to update its cached representation.


14.14 HEAD Requests for Cache Validation

For large resources, APIs may support HEAD requests so clients can inspect metadata without downloading the complete representation.

Example:

http
HEAD /large-resources/123 HTTP/1.1

Response:

http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 428521
ETag: "version-15"

The client can compare the returned entity tag with its locally cached version.

Conditional GET requests using If-None-Match are another commonly used mechanism:

http
GET /large-resources/123 HTTP/1.1
If-None-Match: "version-15"

Response:

http
HTTP/1.1 304 Not Modified
ETag: "version-15"

14.15 Cache Strategies by Data Size

Different cache strategies may be appropriate depending on the size and characteristics of the data.

Small Data Sets

For small data sets, APIs may provide a complete collection endpoint:

http
GET /countries

The complete collection can be associated with an ETag:

http
HTTP/1.1 200 OK
ETag: "countries-v12"

Clients can later check for changes using:

http
GET /countries HTTP/1.1
If-None-Match: "countries-v12"

Medium-Sized Data Sets

For medium-sized collections, combine caching with pagination:

http
GET /products?limit=100&cursor=abc123

The API may provide entity tags for pages or collection versions.

This prevents clients from repeatedly downloading unchanged collections while still avoiding excessively large responses.


Large Resources

For individual large resources, consider supporting:

  • HEAD
  • ETag
  • If-None-Match
  • Conditional GET

This allows clients to determine whether downloading the full representation is necessary.


14.16 Prefer Service-Side or Gateway Caching When Appropriate

Generic HTTP client and proxy caching can become complex because caches must correctly interpret:

  • Cache-Control
  • Vary
  • ETag
  • Authorization
  • User-specific representations
  • Content negotiation

For many systems, caching directly at the application, service, or API gateway layer can be easier to control.

Typical architectures include:

text
Client
   |
   v
API Gateway
   |
   +---- Cache
   |
   v
API Service
   |
   +---- Distributed Cache
   |
   v
Database

Possible caching technologies include:

text
API Gateway Cache
Application Memory Cache
Distributed Cache
Database Query Cache

The exact implementation is infrastructure-specific, but the HTTP semantics exposed to clients should remain well-defined.


14.17 Cache Invalidation

Caching cannot be designed independently from resource updates.

Before enabling caching, define:

  1. What is cached?
  2. How long can it remain cached?
  3. What happens when the underlying resource changes?
  4. How is stale data detected?
  5. How is stale data invalidated?
  6. Is the cached representation user-specific?
  7. Can it safely be shared between clients?

For example:

text
GET /countries

may be suitable for relatively long caching because country reference data changes infrequently.

By contrast:

text
GET /accounts/123/balance

may require very short caching or no caching because the value may change frequently and may be user-specific.


14.18 Performance Optimization Summary

REST API performance should primarily be improved by avoiding unnecessary work and unnecessary data transfer.

The main techniques are:

Technique Primary Benefit
Compression Reduces transferred bytes
Partial responses Avoids returning unused fields
Pagination Limits collection response size
ETag Identifies resource versions
If-None-Match Avoids re-downloading unchanged resources
If-Match Supports safe conditional modifications
Cache-Control Defines cache policy
Vary Prevents incorrect reuse of cached variants
HEAD Retrieves metadata without a response body
Prefer: return=minimal Avoids unnecessary response payloads
Prefer: respond-async Allows asynchronous processing

A well-designed API should optimize performance without weakening the clarity and predictability of its HTTP semantics.

The key principle is:

Do not transfer, compute, or regenerate data when the client does not need it.

Performance | Remi Resume