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.
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:
- Compress request and response payloads where appropriate.
- Allow clients to request only required resource fields.
- Use
ETagand conditional requests to avoid transferring unchanged resources. - Use the
Preferheader when clients can accept reduced or asynchronous processing. - Use pagination for large collections.
- Cache resources that are stable or rarely modified.
For example:
GET /users/123 HTTP/1.1
Accept: application/json
Accept-Encoding: gzip
A server may respond with:
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:
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/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:
Accept-Encoding: gzip
should be able to receive the normal representation:
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:
GET /users/123 HTTP/1.1
Response:
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:
GET /users/123?fields=(name,friends(name)) HTTP/1.1
Response:
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:
fields=(name)
returns only:
{
"name": "John Doe"
}
While:
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:
<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:
fields=(name)
fields=(name,email)
fields=(name,friends(name))
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:
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:
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/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:
GET /users/123 HTTP/1.1
If-None-Match: "7e2f31a"
If the resource has not changed, the server can respond:
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:
ETag: "version-42"
The client can later send:
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:
If-None-Match
is typically used.
For conditional modification:
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:
GET /users?limit=100
or:
GET /users?page=2&page-size=100
Depending on the API design, cursor-based pagination may also be used:
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:
Prefer: return=minimal
and:
Prefer: respond-async
Minimal Response
When the client does not require a complete resource representation after an operation:
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:
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:
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
In particular:
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:
GETHEADPOST
operations where caching has intentionally been enabled.
The documentation should describe relevant headers such as:
Cache-ControlVaryETag
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:
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/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:
Vary: Accept, Accept-Encoding
This informs caches that responses may differ depending on:
Accept
and:
Accept-Encoding
Consider:
GET /users/123 HTTP/1.1
Accept: application/json
Accept-Encoding: gzip
and:
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:
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:
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:
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:
GET /users/123 HTTP/1.1
If-None-Match: "7e2f31a"
If the resource has not changed:
HTTP/1.1 304 Not Modified
ETag: "7e2f31a"
The response must not contain the full resource representation.
If the resource has changed:
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:
HEAD /large-resources/123 HTTP/1.1
Response:
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:
GET /large-resources/123 HTTP/1.1
If-None-Match: "version-15"
Response:
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:
GET /countries
The complete collection can be associated with an ETag:
HTTP/1.1 200 OK
ETag: "countries-v12"
Clients can later check for changes using:
GET /countries HTTP/1.1
If-None-Match: "countries-v12"
Medium-Sized Data Sets
For medium-sized collections, combine caching with pagination:
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:
HEADETagIf-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-ControlVaryETag- 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:
Client
|
v
API Gateway
|
+---- Cache
|
v
API Service
|
+---- Distributed Cache
|
v
Database
Possible caching technologies include:
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:
- What is cached?
- How long can it remain cached?
- What happens when the underlying resource changes?
- How is stale data detected?
- How is stale data invalidated?
- Is the cached representation user-specific?
- Can it safely be shared between clients?
For example:
GET /countries
may be suitable for relatively long caching because country reference data changes infrequently.
By contrast:
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.