releases

BitSpeed Inspect Adds POST Requests and Warm Connections

Until now Inspect only sent GET requests, each on a brand new connection. That’s the right default for comparisons, because every sample does the same work. It isn’t how most real traffic looks, though. APIs take POST bodies, and browsers and clients keep connections open between requests. Inspect 0.3.0 can measure both.

bitspeed-inspect record --method=POST --body-file=query.json --content-type=application/json https://api.example.com/search > post.json
bitspeed-inspect record --connection=warm https://example.org/ > warm.json

Fresh requests to bitspeed.org spend about 77 ms connecting before an 81 ms first-byte wait. Warm requests skip the connection and take about 71 ms in total.

What a reused connection saves

We recorded our own homepage and example.com on GitHub’s runners, ten fresh samples and then ten warm ones each, with the released 0.3.0 binary. Every recording is in the repository.

On the Ubuntu runner, a fresh request to bitspeed.org took 160 ms at the median. Seventy-seven of that was connection setup, DNS, TCP and TLS, before a single byte of the request could go out. Warm, the whole request took 71 ms. example.com went from 115 ms to 43 ms the same way. The macOS runner sits closer to the edge, so its setup cost was smaller, about 15 ms, but the shape matched.

The first-byte wait barely moved. bitspeed.org waited 81 ms fresh and 69 ms warm. That’s the wait we found in the live-site case study, and a reused connection doesn’t touch it. Two different costs, and now you can see them apart: connection setup, which keep-alive removes, and first-byte wait, which it doesn’t.

Each warm hop records whether it reused a connection. In every warm run here, sample 1 opened the connection and samples 2 to 10 reused it. Connection setup is unavailable for those reused samples rather than reported as zero, because nothing was measured.

Why compare won’t mix them

We asked compare to put the fresh run against the warm one anyway. It printed every number and blocked the verdict: the connection policy differs, and so does the reuse pattern. A fresh request and a warm one do different work, so calling one “faster” would be comparing a cold start with a running engine. Compare two warm runs, or two fresh ones.

POST, carefully

A POST body is read from a file, up to 1 MiB, and sent with every sample. The recording keeps its size and SHA-256, never the body. That’s enough for compare to block a verdict when two runs sent different bodies. A short guessable body can be worked out from its hash, so treat recordings as private anyway.

Redirects follow the rules browsers use. 307 and 308 resend the POST, 301, 302 and 303 switch to GET, and each hop records the method it sent. One exception: Inspect won’t follow a 307 or 308 that would carry your body to a different site. It stops and says so.

Keep in mind that POST requests really are sent, ten times by default. Point them at endpoints where that’s safe.

Found before release

An independent review caught one real problem before this shipped. Servers often close idle keep-alive connections. When that happens, Go quietly retries a GET on a new connection. The first warm build saw two connections in one request, failed its own consistency checks and saved nothing at all. Inspect now records the retry honestly: both connection events stay, the reuse flag describes the connection that actually answered, and intervals it can’t attribute to one connection stay unavailable. A POST isn’t retried that way, so on a stale connection that sample is simply recorded as failed.

Plain GET recordings haven’t changed at all. They’re still written in the old format, and Inspect 0.1.1 and 0.2.0 read them, while POST and warm recordings use a new schema 2. Get it from the download page.