Skip to content

Add streaming benchmark scenarios#66

Merged
VikramAditya33 merged 1 commit into
mainfrom
fix/streaming
Apr 29, 2026
Merged

Add streaming benchmark scenarios#66
VikramAditya33 merged 1 commit into
mainfrom
fix/streaming

Conversation

@VikramAditya33
Copy link
Copy Markdown
Collaborator

@VikramAditya33 VikramAditya33 commented Apr 29, 2026

Add streaming benchmark scenarios

Adds comprehensive streaming benchmarks to measure performance of large data transfers.

Changes

New Scenarios:

  • streaming-with-content-length.js - 5MB response stream with Content-Length header (optimal tryEnd() path)
  • streaming-without-content-length.js - 5MB response stream with chunked transfer encoding
  • streaming-upload.js - 4MB POST request body streaming with SHA-256 hash computation

New Assets:

  • wrk-scripts/post-hash-body.lua - Lua script for POST streaming benchmark

Closes #61

Summary by CodeRabbit

  • Tests
    • Added benchmark scenarios for streaming uploads with hash computation performance measurement
    • Added benchmark scenarios to evaluate streaming performance with and without Content-Length headers
    • Added performance measurement scripts for POST request streaming across multiple web frameworks (Fastify, Express, uWestJS)

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 30055c16-577f-4d11-af83-5626c50f5d73

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/streaming

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 29, 2026

uWestJS Benchmark Results

Scenario Express req/s Fastify req/s uWestJS req/s Express throughput Fastify throughput uWestJS throughput uWestJS vs Express uWestJS vs Fastify
compression 6.12k 4.55k 7.69k 3.22 MB/s 2.33 MB/s 3.53 MB/s 1.26x 1.69x
headers 14.91k 18.99k 35.53k 2.77 MB/s 3.55 MB/s 5.59 MB/s 2.38x 1.87x
hello-world 14.67k 23.49k 63.31k 2.43 MB/s 3.94 MB/s 6.28 MB/s 4.32x 2.70x
json-response 13.51k 19.12k 37.99k 3.68 MB/s 5.23 MB/s 9.28 MB/s 2.81x 1.99x
mixed-response 13.31k 18.94k 42.91k 3.07 MB/s 4.39 MB/s 8.68 MB/s 3.22x 2.27x
post-json 12.82k 11.56k 18.73k 2.37 MB/s 3.15 MB/s 3.18 MB/s 1.46x 1.62x
query-params 11.39k 19.09k 43.56k 2.51 MB/s 4.22 MB/s 5.86 MB/s 3.82x 2.28x
route-params 13.27k 19.99k 35.56k 3.03 MB/s 4.57 MB/s 7.09 MB/s 2.68x 1.78x
static-file 15.16k 20.38k 37.30k 150.50 MB/s 201.96 MB/s 369.13 MB/s 2.46x 1.83x
streaming-upload 218.83 210.91 238.08 48.72 KB/s 44.08 KB/s 46.04 KB/s 1.09x 1.13x
streaming-with-content-length 561.21 556.20 537.72 2.74 GB/s 2.72 GB/s 2.63 GB/s 0.96x 0.97x
streaming-without-content-length 492.68 451.62 492.35 2.41 GB/s 2.21 GB/s 2.40 GB/s 1.00x 1.09x

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@benchmarks/scenarios/streaming-upload.js`:
- Around line 26-47: Fastify is rejecting 4MB uploads due to the default 1MB
bodyLimit, so when initializing the Fastify instance (the fastify(...) call that
creates `app`) set the bodyLimit to at least 4 * 1024 * 1024 (or larger) so the
POST /hash-body route and the createHashFromRequest streaming handler can
receive the full payload; update the Fastify server options accordingly to match
the benchmark payload size.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6ab4e759-e547-4985-926c-0305f7b2758a

📥 Commits

Reviewing files that changed from the base of the PR and between 6dbe30c and e2b8aef.

📒 Files selected for processing (4)
  • benchmarks/scenarios/streaming-upload.js
  • benchmarks/scenarios/streaming-with-content-length.js
  • benchmarks/scenarios/streaming-without-content-length.js
  • benchmarks/wrk-scripts/post-hash-body.lua

Comment on lines +26 to +47
if (framework === 'fastify') {
app.post('/hash-body', (req, reply) => {
createHashFromRequest(req.raw, (digest, error) => {
if (error) {
reply.code(500).send({ error: error.message });
return;
}
reply.type('text/plain').send(digest);
});
});
} else {
// Express and uWestJS use same API
app.post('/hash-body', (req, res) => {
createHashFromRequest(req, (digest, error) => {
if (error) {
res.status(500).type('text/plain').send('Error processing upload');
return;
}
res.type('text/plain').send(digest);
});
});
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check benchmark and server setup for Fastify request-body parsing / limits.
rg -n "addContentTypeParser|bodyLimit|application/octet-stream|hash-body" benchmarks src -C 3

Repository: FOSSFORGE/uWestJS

Length of output: 14956


🏁 Script executed:

# Find the benchmark harness entry point and setup files
find benchmarks -name "*.js" -type f | head -20

Repository: FOSSFORGE/uWestJS

Length of output: 745


🏁 Script executed:

# Look for the main benchmark file that initializes the app
rg -n "framework|fastify|express|uwestjs" benchmarks --type js -l | head -10

Repository: FOSSFORGE/uWestJS

Length of output: 411


🏁 Script executed:

# Check if there's a main/index file or setup file in benchmarks root
ls -la benchmarks/

Repository: FOSSFORGE/uWestJS

Length of output: 1068


🏁 Script executed:

cat -n benchmarks/server.js

Repository: FOSSFORGE/uWestJS

Length of output: 4628


🏁 Script executed:

cat -n benchmarks/test-setup.js

Repository: FOSSFORGE/uWestJS

Length of output: 3579


🏁 Script executed:

rg -n "bodyLimit|bodysize" --type json benchmarks/package.json

Repository: FOSSFORGE/uWestJS

Length of output: 43


🌐 Web query:

Fastify default bodyLimit value

💡 Result:

The default value of bodyLimit in Fastify is 1048576 bytes (1 MiB).

Citations:


Fastify benchmark fails: 4 MB request exceeds default 1 MB bodyLimit.

The Fastify server is initialized without a bodyLimit option (server.js:43), defaulting to 1 MB. The wrk script sends a 4 MB application/octet-stream body, so Fastify will reject the request before the streaming handler runs. Configure Fastify with bodyLimit: 4 * 1024 * 1024 or larger to match the benchmark payload size and test actual streaming performance instead of request rejection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@benchmarks/scenarios/streaming-upload.js` around lines 26 - 47, Fastify is
rejecting 4MB uploads due to the default 1MB bodyLimit, so when initializing the
Fastify instance (the fastify(...) call that creates `app`) set the bodyLimit to
at least 4 * 1024 * 1024 (or larger) so the POST /hash-body route and the
createHashFromRequest streaming handler can receive the full payload; update the
Fastify server options accordingly to match the benchmark payload size.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the scenario uses streaming (req.raw for Fastify), so it shouldn't be buffering the body. if there's is a problem suggest the changes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's an existing content type parser registration in server.js or test-setup.js
rg -n "addContentTypeParser|octet-stream" benchmarks/

Length of output: 852


@VikramAditya33, you're right that bodyLimit is not the problem here — Fastify only enforces the body limit when using the parseAs: 'string'|'buffer' option; if the limit is exceeded, the custom parser will not be invoked. Without parseAs, Fastify does not buffer the stream and the limit is irrelevant. So streaming via req.raw is safe from the bodyLimit concern.

However, there is still a real issue: no content type parser is registered for application/octet-stream, and without one, Fastify will reject the POST request with a 415 Unsupported Media Type error before the route handler ever runs.

The official Fastify pattern for raw body streaming is to register a no-op content type parser that simply calls done(), then access request.raw directly in the handler for piping/streaming.

Here is the fix — add a no-op addContentTypeParser call in the Fastify branch of setup:

if (framework === 'fastify') {
  // Required: register a no-op parser so Fastify doesn't reject the
  // application/octet-stream body with 415 before the handler runs.
  // Using the streaming (non-parseAs) form means bodyLimit does NOT apply.
  app.addContentTypeParser('application/octet-stream', function (request, payload, done) {
    done();
  });

  app.post('/hash-body', (req, reply) => {
    createHashFromRequest(req.raw, (digest, error) => {
      if (error) {
        reply.code(500).send({ error: error.message });
        return;
      }
      reply.type('text/plain').send(digest);
    });
  });
}

This tells Fastify to accept application/octet-stream without buffering anything, leaving the raw Node.js stream untouched so createHashFromRequest(req.raw, ...) can consume it correctly.


🧠 Learnings used
Learnt from: VikramAditya33
Repo: FOSSFORGE/uWestJS PR: 60
File: src/http/handlers/compression/compression-handler.ts:334-338
Timestamp: 2026-04-28T19:19:30.852Z
Learning: In the uWestJS project (src/http/core/response.ts), `uwsRes.end(buffer)` (the native uWebSockets.js layer) automatically sets the `Content-Length` header based on the buffer size. Therefore, explicitly calling `res.setHeader('content-length', ...)` before `res.send(buffer)` results in a duplicate Content-Length header and triggers HPE_UNEXPECTED_CONTENT_LENGTH. The correct pattern is to let uWS handle Content-Length automatically via `end()`.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay makes sense

@VikramAditya33 VikramAditya33 merged commit eefa6df into main Apr 29, 2026
4 checks passed
@VikramAditya33 VikramAditya33 deleted the fix/streaming branch April 29, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add streaming benchmark scenarios

1 participant