Skip to content

perf: bandwidth/browser measurement harness + 74 MB cold-load findings (#313, #345) - #346

Open
rdhyee wants to merge 1 commit into
isamplesorg:mainfrom
rdhyee:perf/313-bandwidth-findings
Open

perf: bandwidth/browser measurement harness + 74 MB cold-load findings (#313, #345)#346
rdhyee wants to merge 1 commit into
isamplesorg:mainfrom
rdhyee:perf/313-bandwidth-findings

Conversation

@rdhyee

@rdhyee rdhyee commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Companion to #345 and #313.

🤖 rbotyee (Claude, operated by @rdhyee) — Raymond's intent: overnight work on making isamples.org more robust at scale. What I did: measured the Explorer across connection speeds and browsers, root-caused a large cold-load cost, and proved the cause with a controlled A/B. Filing because the fix lives in the Cloudflare Worker, not this repo.

Summary

A cold load of the Interactive Explorer transfers ~74 MB before the facet panel appears. Our own docs claim "typically less than 1 MB for initial exploration" (index.qmd) and "only the bytes you need are transferred" (explorer.qmd). Both are wrong by roughly two orders of magnitude.

The whole thing comes down to one status code.

DuckDB-WASM decides whether a server supports partial downloads by sending a HEAD request with a Range header. data.isamples.org answers 200 instead of 206, so DuckDB concludes the server can't do partial reads and downloads every file whole — including samples_map_lite_v3.parquet (62.9 MB) when it actually needs about 1.5 MB of it.

Proposed fix

In the Cloudflare Worker fronting data.isamples.org: when a HEAD carries a Range header and the object supports ranges, respond 206 with Content-Range (no body, per HEAD semantics) rather than 200.

Content-Range is already in Access-Control-Expose-Headers, so nothing else needs to change.

Evidence — controlled A/B, not inference

A transparent reverse proxy forwarded every request to the real host unchanged, altering exactly one thing: HEAD+Range206. Same build, same cold cache, one variable.

Unthrottled

| | Control (200) | Treatment (206) |
|


See #345 for the full write-up and the proposed Worker fix. This PR only adds the measurement instruments and the findings document — no application code changes, and the Worker fix is not applied here (it lives outside this repo).

🤖 Generated with Claude Code

https://claude.ai/code/session_01QCCDurpcLzMe7L72y2HDAa

… MB cold load

Answers isamplesorg#313 (open since 2026-06-26, never done: what browser/OS/bandwidth
combinations does the Explorer work in?) and root-causes the 'falling back to
full HTTP read' finding from the 2026-08-05 coherence audit.

HEADLINE: a cold load of the default world view transfers ~74 MB. The site's own
docs claim 'typically less than 1 MB for initial exploration'. Wrong by ~2 orders
of magnitude.

ROOT CAUSE: DuckDB-WASM probes range support with a HEAD carrying a Range header.
data.isamples.org answers 200 instead of 206, so DuckDB concludes the server
cannot do partial reads and downloads every file whole — including
samples_map_lite_v3.parquet (62.9 MB) when it needs ~1.5 MB of it.

PROVEN, not inferred. A transparent reverse proxy forwarded everything unchanged
except HEAD+Range -> 206. Same build, same cold cache, one variable:
  unthrottled   74,202,598 B -> 3,341,812 B; 8 full-read fallbacks -> 0
  3g-fast       facet panel 440.6s -> 94.2s (4.7x), 74.2 MB -> 3.0 MB
Fix is server-side in the Cloudflare Worker; no application code changes.

WHY IT WAS MISSED: the server was cleared three times by curl tests that used
GET. GET+Range correctly returns 206; HEAD+Range returns 200. Only the verb
DuckDB actually sends was wrong. Documented so the next person checks HEAD.

isamplesorg#313 RESULTS (production, cold cache, desktop):
  unthrottled  globe 2.2s  facets 10.8s
  4g           globe 15.4s facets 168.7s
  3g-fast      globe 38.7s facets 423.1s
  3g-slow      globe 156.6s facets NEVER (>600s budget)
Cross-browser: works in Chromium, Firefox AND WebKit, desktop and mobile, zero
uncaught page errors, comparable timings — that had been an open unknown. The
74 MB is identical in all three, so it is not a browser quirk.

Adds two committed, reproducible instruments:
  tests/playwright/bandwidth_matrix.py — the measurement harness
  tests/playwright/range_fix_proxy.py  — the A/B proxy that isolates the cause

INSTRUMENTATION TRAPS documented in the report, both of which produced
confidently wrong results before being caught:
  - page-level CDP does NOT see Web Worker traffic. DuckDB runs in a worker, so
    the page session reported 568 KB / 3 requests — a textbook 'only the bytes
    you need' result, off by 130x. Real accounting uses context-level events.
  - the instrument changed the measurement twice: waitForDebuggerOnStart paused
    workers that were never resumed (globe never rendered), and route
    interception inflated time-to-globe from 2.5s to 7.3s. Final harness is
    passive.

No application code changed. No fix applied yet — the Worker change is not in
this repo and wants RY's call.

Refs isamplesorg#313

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QCCDurpcLzMe7L72y2HDAa
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.

1 participant