envoyproxy / envoyproxy/envoy

Support buffering of requests with large payloads

Open
#40,028 2 comments 0 reactions 1 assignee Claimed by @yanavlasov View on GitHub
enhancement no stalebot
Dominant language
C++
Stars
28.9k
Forks
5.6k
Avg merge
1d 22h
Merged PRs (30d)
430

Description

### Objective
Implement buffering mechanisms in Envoy for requests with large bodies. The specific use case is serving Inference (AI/ML) workloads.

**Requirements:**

- Support buffering of requests with large payloads,
- Support request retries,
- Support flow control when sending buffered content to the destination host,
- Integrate with the overload manager stream buffer size accounting and actions,
- Support copy-on-write modifications to the request body in Envoy extensions.

### Background
Inference requests, while served over HTTP, are sufficiently different from the garden variety of HTTP requests. Garden variety of HTTP requests are typically forwarded based on HTTP header values, while the body is streamed in small increments and normally is immutable. This allows Envoy to forward HTTP requests with large bodies using a small amount of memory.
However this does not work well for forwarding of Inference requests. The forwarding destination of Inference requests may be determined by the content of the body, instead of headers, and requires buffering of the entire body, since request is represented as a JSON payload.
Buffering of the entire body is also needed to support retries of failed requests.
Additionally, Inference gateways may have to modify the contents of the body if the request schema of the destination service is different from the gateway’s schema. This use case is further complicated by supporting request failover from one service provider to another with potentially different request schemas, necessitating multiple rewrites of the body.

### Details
Presently Envoy limits how much request body can be processed at a time. This limit is determined by the [size of the buffer](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/listener/v3/listener.proto#envoy-v3-api-field-config-listener-v3-listener-per-connection-buffer-limit-bytes) for processing low level network I/O events. Normally this buffer is a few tens of kilobytes in size. The size of this buffer also determines the maximum size of the body of requests that can be retried. This size can be currently reduced in the per-route [configuration](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-virtualhost-per-request-buffer-limit-bytes).
To allow Envoy to buffer larger request bodies a new route level configuration value is added - an [UInt64Value](https://protobuf.dev/reference/protobuf/google.protobuf/#uint64-value) request_body_buffer_limit, overriding buffering limits set by existing configuration values. The existing [per_request_buffer_limit_bytes](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-virtualhost-per-request-buffer-limit-bytes) value can not be re-used as it is a 32 bit quantity and it is conceivable that some operators may need to support bodies larger than 4Gb. When configured Envoy will buffer the request body up to the **request_body_buffer_limit** size and not limited by the network I/O buffer size. The operator would set the **request_body_buffer_limit** value to be enough to serve maximum expected request size or even just under the total memory limit of the Envoy process, to prevent a single large request from exhausting Envoy memory before the overload manager can take action.
The amount of memory used by request buffers is already accounted for in the overload manager and will allow it to apply all relevant mitigations under high memory pressure, such as resetting of streams that consume the most memory (note that this action still needs to be implemented for H/1 protocol) or triggering scale out operations.
#### Flow Control
However buffering of entire request bodies will make existing flow control ineffective, since it works by applying backpressure on the producer peer. If the entire body had already been received from the client, application of backpressure by the upstream server will do nothing, causing Envoy to overwhelm the upstream server if it has a large amount of data available for sending.
To prevent this, Envoy will only send a buffered body when the upstream network buffer has available space and limit the amount of data it sends at a time to a fraction of the size of the low level network buffer [per_request_buffer_limit_bytes](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route_components.proto#envoy-v3-api-field-config-route-v3-virtualhost-per-request-buffer-limit-bytes) to achieve a more fair sharing of multiplexed connections. This additional flow control will be implemented in the router filter, and is similar to processing deferred HTTP/2 BODY frames.

Practical buffer sizes for Inference requests
While there are models that accept 1M token length, most models have a 128K token limit. With the assumption of 5 bytes per token a buffer of 640Kb would be enough to buffer largest requests for most models. 1Mb would be plenty including JSON overhead. 1Mb stream window for HTTP/2 is fairly common. Thus enabling retry buffers with up to 1Mb in sizes is within common buffering limits for HTTP/2 protocol and should not require adjustments to task shapes.

### Multiple Rewrites of the Original Body
Inference gateways allow clients to send requests using its own well defined schema and transparently rewrite the request using the schema of the destination service. This simplifies application development by abstracting details of issuing requests to different service providers. However this makes it challenging to implement failover between services with different request schema. In the worst case scenario the body needs to be duplicated before sending it to the upstream service.
This duplication is very expensive for large request bodies and can be optimized in certain scenarios. For example if the body mutation occurs in the upstream filter chain (see the [Request Flow documentation](https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#request-flow) for more information), the upstream filter manager can use copy-on-write semantics to reduce the amount of memory duplication if only a portion of the request body needs to be modified or the modification occurs in the ext_proc service callout.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.