-
Notifications
You must be signed in to change notification settings - Fork 656
Performance
Titanium targets low-overhead MITM proxying: connection pooling, HTTP/2 multiplexing, and buffer reuse. Figures below were measured on one Windows 11 / .NET 10 machine with RpsLoadProbe and the Basic example in Release. Treat them as orientation, not a guarantee—re-run on your hardware.
For pooling knobs and certificate first-visit tuning, see Performance and pooling.
| What | Result |
|---|---|
| HTTPS TTFB vs direct (median, 14 hosts) | Cold ≈ parity (−1 ms); warm −25 ms (proxy faster) |
| HTTP/1 loopback GET (no body intercept) | ~186 µs, ~17.5 KB allocated / request |
| Cleartext reverse HTTP/1 peak | ~16.0k RPS (TWP) vs ~15.5k RPS (nginx/Windows) |
| TLS-terminate reverse HTTP/1 peak | ~24.7k RPS (TWP) vs ~13.0k RPS (nginx/Windows) |
| TLS-terminate H2→H1 cleartext peak | ~7.6k RPS @ c=64, 0% err (TWP) · nginx ~14.2k @ c=32 (fails SLO at c=64) |
| Reverse HTTP/3 (MITM to Quic origin) | see prior compare-tls tables |
| Explicit HTTPS MITM peak | ~13.6k RPS |
| Basic example footprint (Release, after load) | ~74 MB working set · ~24–29 MB private bytes |
For tiny JSON responses (~64 B) on loopback, that ordering is not expected:
- Topology dominates protocol. Always compare arms that share the same crypto hop count and upstream protocol.
- HTTP/2/3 shine at multiplexing, not at maximizing single-origin tiny-GET RPS.
-
Fair terminate topology (client TLS → cleartext origin) is what nginx uses for H2. TWP matches that with
ForwardCleartext+ the H2→H1 bridge (and H1 TLS terminate).
Under multiplexed load the bridge used RespondStreaming (HEADERS without END_STREAM + DATA). .NET HttpClient reported Received an HTTP/2 pseudo-header as a trailing header and error rates climbed with concurrency. The bridge also omitted IsExternalBridge, racing Http2Helper against the synthetic emitter.
Fix: mark the stream IsExternalBridge, buffer the origin body, and emit via the buffered synthetic path. Keep-alive pooling remains enabled with residual-buffer and lease guards.
Machine: Windows 11 (10.0.26200), 11th Gen Intel Core i7-1185G7 @ 3.00 GHz (8 logical), 31.8 GiB RAM, .NET 10.0.10, nginx/Windows 1.31.3.
pwsh tools/RpsLoadProbe/run-rps.ps1 -Mode compare-terminateCSV: tools/RpsLoadProbe/results/rps-ramp-20260816-045803.csv (warmup 2s / measure 8s, c=8,32,64).
| Arm | Topology | Sustainable | Peak | Notes |
|---|---|---|---|---|
| TWP H1 TLS | Client TLS → cleartext H1 | 24,689 @ 64 | 24,689 | 0% err |
| nginx H1 TLS | ssl → cleartext H1 | 12,693 @ 64 | 13,010 | 0% err |
| TWP H2→H1 | Client h2 TLS → H2→H1 bridge → cleartext H1 | 7,554 @ 64 | 7,554 | 0% err (stable at c=64) |
| nginx H2 | Client h2 TLS → cleartext H1 | 14,175 @ 32 | 14,175 | fails SLO at c=64 |
| TWP H3→H1 | Client h3 → cleartext H1 | — | ~1.8k | errors (stream abort 258) — follow-up |
nginx H2 still leads peak RPS on this machine; TWP H2→H1 is the first zero-error fair topology and stays within SLO at c=64 where nginx does not.
| Client | Upstream | TWP | nginx/Windows | Mode |
|---|---|---|---|---|
| H1 cleartext | H1 cleartext | yes | yes | compare |
| H1 TLS | H1 cleartext | ForwardCleartext |
ssl proxy_pass http://
|
compare-terminate |
| H2 TLS | H1 cleartext | H2→H1 bridge + ForwardCleartext
|
ssl+http2 → cleartext | compare-terminate |
| H2 TLS | H2 TLS (MITM) | native h2↔h2 | n/a (nginx terminates) |
compare-tls / reverse-http2
|
| H2 TLS | H2 cleartext (h2c) | not supported (no h2c) | uncommon | — |
| H3 QUIC | H3 QUIC | MITM | no QUIC on nginx/Windows | reverse-http3 |
| H3 QUIC | H2 cleartext/TLS | bridge paths exist; h2c N/A | — | — |
| H3 QUIC | H1 cleartext |
ForwardCleartext + Http11 |
— |
reverse-http3-cleartext (WIP) |
There is no artificial upper clamp on server defaults. Per-endpoint overrides:
| Knob | Scope | Default | Override |
|---|---|---|---|
ProxyServer.MaxCachedConnections |
process, per upstream host | 128 | any ≥ 1 |
ProxyEndPoint.MaxCachedConnections |
endpoint → pool depth for that EP’s sessions | null (use server) | e.g. 256 on reverse EP |
ProxyEndPoint.MaxConcurrentClients |
endpoint admission | null (off) | any ≥ 1 |
ResourceLimits.MaxConcurrentStreamsPerConnection |
H2 streams | 256 | ProxyResourceLimits.Create(...) |
TransparentQuicProxyEndPoint.MaxInboundBidirectionalStreams |
H3 | 100 (probe uses 256) | property on EP |
ForwardCleartext |
transparent TLS terminate | false |
true + decrypt |
proxy.MaxCachedConnections = 512;
proxy.ResourceLimits = ProxyResourceLimits.Create(
/* … */,
maxConcurrentStreamsPerConnection: 1000,
maxCachedConnectionsPerHost: 512,
/* … */);
var ep = new TransparentProxyEndPoint(IPAddress.Any, 443, decryptSsl: true)
{
ForwardHost = "127.0.0.1",
ForwardPort = 8080,
ForwardCleartext = true,
MaxCachedConnections = 256, // deeper pool for this reverse EP only
GenericCertificateName = "example.com"
};
ep.BeforeSslAuthenticate += (_, a) =>
{
a.UpstreamHttpProtocol = UpstreamHttpProtocol.Http11;
a.AllowHttpProtocolTranslation = true; // H2 client → H1 origin bridge
return Task.CompletedTask;
};Unchanged from prior wiki revision — see curl median Δ and BenchmarkDotNet tables in git history if needed; re-run:
dotnet run -c Release --project benchmarks/Titanium.Web.Proxy.Benchmarks -- --filter '*Throughput*'| Metric | Approx. value |
|---|---|
| Working set | ~74 MB |
| Private bytes | ~24–29 MB |