Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
- [Creating new JS components](./creating-new-js-components.md)
- [Transpiling](./transpiling.md)
- [Example Workflow](./example.md)
- [Performance]()
- [Benchmarks]()
- [Development-use `jco serve`](./performance/benchmarks/development-use-jco-serve.md)
- [Advanced]()
- [Optimized Host Bindings](./advanced/optimized-host-bindings.md)
- [WIT Type representations](./advanced/wit-type-representations.md)
Expand Down
122 changes: 122 additions & 0 deletions docs/src/performance/benchmarks/development-use-jco-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Development-use `jco serve`

`jco serve` is a convenient development utility for running Preview 2 HTTP components (in particular
components that export the `wasi:http/incoming-handler` interface) in Node.js.

> [!WARNING]
> `jco serve` is intended for development and testing only.
>
> It is not production ready, and these benchmarks should not be
> interpreted as production deployment guidance.
>
> `jco serve` does not yet support Preview 3 HTTP components.

## Caveats

Given the "for development" development nature of `jco serve`, this benchmark measures Jco's
Node.js development server, not the general performance of Preview 2 HTTP components.

A production deployment would typically use [Wasmtime][wasmtime] or another production-oriented
component runtime, which is substantially more efficient than `jco serve`.

Performance depends on the component, generated bindings, JavaScript runtime, hardware, and workload.
Measurements from one application or machine should not be treated as production capacity estimates.
Use this benchmark to compare development-server configurations rather than to estimate production
capacity.

[wasmtime]: https://wasmtime.dev

## Request isolation

By default, `jco serve` reuses a single instantiated component across requests.

The `--isolate-requests=instance` argument introduces a light (and permeable) layer of isolation:
a fresh component instance while retaining the same JavaScript isolate and module cache.

The stronger `--isolate-requests=worker` mode creates a worker thread with a separate V8
isolate and module cache for every request.

> [!NOTE]
> The `--isolate-requests` option selects worker isolation by default

### Worker mode efficiency

Workers are one-shot: each handles exactly one request and is then terminated and replaced, so
prewarming does not reuse JavaScript globals or module caches across requests.

As the cost of isolated workers is quite high, Worker mode maintains a pool of 50 prewarmed workers by default.

The capacity can be adjusted with `--isolate-worker-pool-size`. Larger pools can hide worker
startup latency during bursts, at the cost of higher server startup time and memory use.

## Running the benchmark

Jco includes an opt-in end-to-end benchmark that compares both isolation modes with a shared
component and a native Node.js HTTP handler:

```console
pnpm --filter @bytecodealliance/jco bench:serve-isolation
```

> [!NOTE]
> The benchmark is not part of the normal test suite.

The `serve-isolation` benchmark suite builds a minimal HTTP component, starts a real
NodeJS server for each mode, and makes sequential requests to expose per-request overhead. It reports
requests per second and mean, median, and p95 latency.

By default, each mode receives 100 warmup requests followed by 10,000 measured requests.

Worker isolation intentionally creates a worker for every request, so a complete default run can take
a long time.

Based on the reference result below, benchmarking all three worker pool sizes may take a while.
A smaller smoke run can be requested before committing to the full benchmark:

```console
JCO_SERVE_BENCH_WARMUP=5 \
JCO_SERVE_BENCH_REQUESTS=30 \
pnpm --filter @bytecodealliance/jco bench:serve-isolation
```

The reference result recorded below can be reproduced with its original sample sizes:

```console
JCO_SERVE_BENCH_WARMUP=10 \
JCO_SERVE_BENCH_REQUESTS=100 \
pnpm --filter @bytecodealliance/jco bench:serve-isolation
```

Componentization is deliberately excluded from the timed measurements. Because building the fixture
can still make repeated benchmark runs inconvenient, the generated component can be retained and
reused:

```console
JCO_SERVE_BENCH_COMPONENT_OUT=/tmp/jco-serve-benchmark.wasm \
pnpm --filter @bytecodealliance/jco bench:serve-isolation

JCO_SERVE_BENCH_COMPONENT=/tmp/jco-serve-benchmark.wasm \
pnpm --filter @bytecodealliance/jco bench:serve-isolation
```

### Reference result

Using Node.js 24.19.0 on a 6-vCPU Intel Xeon 2.60 GHz virtual machine, the benchmark made 10 warmup requests
followed by 100 measured sequential requests per mode:

| Mode | Requests/s | Mean latency | Median latency | p95 latency |
| --------------------- | ---------: | -----------: | -------------: | ----------: |
| Native `node:http` | 598.13 | 1.67 ms | 1.69 ms | 2.03 ms |
| Shared component | 225.07 | 4.44 ms | 4.31 ms | 5.40 ms |
| Instance isolation | 22.88 | 43.71 ms | 43.24 ms | 47.59 ms |
| Worker, pool size 1 | 4.15 | 240.95 ms | 239.36 ms | 274.28 ms |
| Worker, pool size 50 | 13.55 | 73.82 ms | 70.14 ms | 100.00 ms |
| Worker, pool size 100 | 14.78 | 67.66 ms | 65.14 ms | 95.25 ms |

> [!NOTE]
> The performance ratios here are likely more improtant than the absoltue numbers.

This result illustrates the relative cost of the isolation mechanisms for a minimal component.

A larger component, concurrent traffic, different response sizes, pool utilization, or another Node.js
version can change both the absolute results and the ratios between modes.
22 changes: 22 additions & 0 deletions packages/jco/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,32 @@ Using the preview2-shim WASI implementation, full access to the underlying syste

For HTTP Proxy components, `jco serve` provides a JS server implementation:

> **Warning:** `jco serve` is intended for development and testing only. It is not production ready.

```
jco serve --port 8080 server.wasm
```

By default, the server reuses one component instance for all requests. Pass `--isolate-requests`
to run every request in a fresh Node.js worker thread (equivalent to
`--isolate-requests=worker`):

```
jco serve --isolate-requests --port 8080 server.wasm
```

Worker isolation gives each request a separate V8 isolate, JavaScript global scope, module cache, and
component instance. Request and response bodies are streamed through a local HTTP proxy. For lower
overhead, `--isolate-requests=instance` creates fresh component memories, tables, globals, and resource
tables while sharing the JavaScript isolate and module cache. Neither mode is a security sandbox or
isolates external filesystem and network side effects. Both modes have a significant performance cost,
especially worker isolation. Instance isolation is generally the faster isolation mechanism; worker
isolation trades additional worker startup and HTTP proxy overhead for separate JavaScript globals and
module caches. To reduce per-request startup latency without reusing JavaScript state, worker mode
prewarms 50 one-shot workers by default; configure this with `--isolate-worker-pool-size`. See the
[development-use `jco serve` benchmark](https://bytecodealliance.github.io/jco/performance/benchmarks/development-use-jco-serve.html)
page for benchmark results and reproduction instructions.

> [Wasmtime](https://github.com/bytecodealliance/wasmtime) generally provides the most performant implementation for executing command and proxy worlds to use. These implementations are rather for when JS virtualization is required or the most convenient approach.

### Componentize
Expand Down
1 change: 1 addition & 0 deletions packages/jco/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"build:ts": "tsc -p tsconfig.json",
"build:types:preview2-shim": "pnpm run build:types:preview2-shim --include-workspace-root",
"build:test:components": "cargo xtask build-test-components",
"bench:serve-isolation": "node test/bench/serve-request-isolation.js",
"fmt": "oxfmt",
"fmt:check": "oxfmt --check",
"lint": "oxlint",
Expand Down
Loading
Loading