The response size of an AWS Lambda function can be reduced with compression

A Lambda function invoked via its function URL returns the response to the browser as-is without any additional compression. I have a function that produces a sizable response, sometimes over 2 megabytes:
content-length: 2444019
The code is straightforward for this:
const body = "...",
return {
isBase64Encoded: false,
body,
}
Clients can tell the function whether they support compression or not via the accept-encoding
header:
accept-encoding: gzip, deflate, br, zstd
This means the Lambda could inspect this header, and if it sees a compression that it supports then it can return an encoded response. Depending on the type of response, this can save a significant amount of bytes sent over the network.
It turns out that implementing this is rather straightforward as well. I opted for supporting only Brotli as that is supported by most browsers and I don't care much about the performance of anything else.
The code checks if the accept-encoding
includes br
, then compresses the response:
import zlib from "node:zlib";
import {promisify} from "node:util";
const acceptEncoding = event.headers["accept-encoding"];
if (acceptEncoding && acceptEncoding.split(",").some((v) => v.split(";")[0].trim() === "br")) {
const bodyBuffer = Buffer.from(body, "utf8");
const compressedBody = await promisify(zlib.brotliCompress)(bodyBuffer, {params: {
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT,
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: bodyBuffer.length,
}});
return {
headers: {
"Content-Encoding": "br",
},
isBase64Encoded: true,
body: compressedBody.toString("base64"),
};
}else {
// handle non-compressed response
}
The same response that is 2.4 megabytes uncompressed is just 75K using Brotli:
content-encoding: br
content-length: 75266
The downside to this is the increased CPU usage of the Lambda function. Since the total download time does not matter for the function execution, compression makes the function a bit more expensive. In practice I don't think that it's ever significant.
There is some additional optimizations left for the Lambda response. By default the function uses a buffer for the result that waits for the execution to finish and only then start sending data to the client.
The alternative is to use the streaming response. This unlocks better TTFB (Time to First Byte) and unlocks higher response sizes (20 MB vs the default 6 MB). I explored this in this article: How to use the AWS Lambda streaming response type.