Security Data Works

Writing · Benchmark methodology

How to run a benchmark that doesn't lie.

A benchmark is a measurement, and a measurement you can't trust is worse than no measurement at all, because it carries the authority of a number while pointing in the wrong direction. I learned the rules below the way you'd expect, which is by watching my own lab nearly publish a result that was confidently, precisely false, and the rules are not clever. They're the boring discipline that separates a number you can stand behind from a number that just happens to be the one that came out, and the first of them comes before any clock is started at all. Two larger arguments sit on either side of the rules and frame why they matter: the contract clauses that quietly make most published benchmarks vendor-funded by construction, and the harder lesson that when an instrument breaks the way it breaks is itself a reading you should not throw away.

The six rules

The whole method comes down to six rules, each earned by a finding in the SDW Lab below and set here in the order they apply:

  • verify the answer before you trust the clock,
  • report the noise, and scale until the signal clears it,
  • matching a codec is not config parity,
  • isolate the run, or measure the contention instead,
  • control the environment, including the power plan,
  • and hash the logical rows, not the bytes.

Two larger arguments sit on either side of the rules, the contract clauses that make most published benchmarks vendor-funded by construction, and the harder lesson that when an instrument breaks the way it breaks is itself a reading.

The rule that comes first

Verify the answer before you trust the clock.

I want to open with the case that taught me to put this rule above all the others, because it's the one that nearly got past me. I was running a cross-engine scale ladder, the same simple analytical queries executed by ClickHouse's embedded engine, chDB 4.1.8, and by DuckDB, over byte-identical Parquet, the whole exercise built to ask which engine was faster on security-shaped data. Almost as an afterthought I'd put an answer-equality check at the front of the harness, on the principle that there's no point reporting that one engine is 8% faster than another if the two aren't even computing the same thing. Before timing anything, the harness runs each query on each engine once and compares the results, and I expected that check to be a formality. At 100 million rows it failed: a selective count(*) with an equality filter came back from chDB tens of rows short of the count DuckDB computed over the same files, no exception, no warning, no log line flagging a skipped row group. The query compiled, the engine ran it fast, and it returned a number that was simply too small.

The part worth sitting with is the counterfactual. If I'd trusted the timings, and fast is exactly what a performance benchmark is built to reward, the run would have produced a clean, plausible, publishable table showing chDB holding its own against DuckDB at scale, and the missing rows would have ridden along inside a result that looked completely normal. The faster the wrong engine, the more convincing the bad number, which inverts the usual intuition that a quick answer is a trustworthy one. A timing-only benchmark would have laundered a silent wrong answer into a performance win and published it as one. The only reason it didn't is that the gate I almost didn't bother building compared the two answers before it compared the two clocks. The full case, what the bug was and how I isolated it to one engine's equality-pushdown path on the tail row groups of the file, is its own essay (the query engine returned the wrong answer); what matters for methodology is the order of operations. Correctness is the gate, and timing runs only after the answers agree. Everything else in this piece is downstream of getting that order right.

So rule zero is to verify the answer before you trust the clock, which on generated data is nearly free because you know the ground truth by construction, and on real data means computing a trusted reference answer once and pinning it. Run the same query on every engine and on the reference, fail loudly the moment any two disagree, before a single timing is recorded so a divergence stops the run rather than getting averaged into a result. One refinement the lab handed me later is that "disagree" needs a type-aware definition: the same column of floating-point values summed by five engines lands on three subtly different totals from ordinary IEEE-754 rounding, all of them correct, while the integer counts and sums agree to the last bit, so the gate compares integer-typed answers exactly and floating-point ones within a tolerance rather than crying wolf over benign rounding. The rest of these rules make the speed number honest once you've established that the answers it describes are real.

Rule one

Report the noise, and scale until the signal clears it.

Every comparative result should carry its coefficient of variation, the run-to-run standard deviation as a percentage of the mean, because a difference between two engines means nothing until you know how much the same engine varies against itself. This is the rule that decides whether a benchmark is measuring an engine or measuring the weather, and the lab finding that earned it is uncomfortable. At 1 million rows the chDB queries finished in 5 to 50 milliseconds, and at that scale the coefficient of variation ran as high as 55%, which means the spread between a fast run and a slow run of the identical query on the identical engine was wider than most of the differences I'd have been tempted to report between engines. Any "engine A beat engine B" call in that regime is a coin flip wearing the costume of a result.

The variation settled as the work grew. By 10 million rows the coefficient of variation came down to around 5%, and by 100 million rows to around 4%, which is the actual reason you run large. It isn't that big numbers are more impressive; it's that scale is how you get the per-query work to dominate the fixed-cost jitter, the process startup and the cache effects and the scheduler noise that swamp a sub-millisecond query, until the signal you care about finally clears the noise you can't avoid. A surprising amount of published benchmarking gets run at a scale where the headline difference sits inside the error bars, and the author either doesn't compute the variation or doesn't report it, so the reader can't tell whether the 8% gap is a real property of the engine or this morning's luck. The discipline is to scale up until the coefficient of variation is small relative to the effect you're claiming, and if it won't come down, to say so and stop claiming the effect.

A few mechanics ride along with the rule and they're the unglamorous part that separates a measurement from an anecdote. Warm versus cold matters, because the first run pays for cold caches and file-system reads that later runs don't, so mixing a cold first run into the average inflates both the mean and the variance and you should decide deliberately which regime you're measuring. Trial count matters, because you can't estimate a coefficient of variation from two runs, and the noisier the system the more trials you need before the CV itself stabilizes. And a single run is never a measurement, full stop, because one number carries no information about its own reliability, which is the whole failure this rule is built to catch. The practical line that falls out of it is specific rather than philosophical: on this kind of workload any query that finishes in under about 100 milliseconds and any scale below roughly 10 million rows is noise-dominated, so a "23% faster" headline produced at a million rows with no CV reported is an artifact rather than a weak result, and you should treat it the way you'd treat a coin that came up heads once. The benchmark didn't get more honest because the engine got faster, it got more honest because the noise stopped being able to hide inside it.

The corollary I want to flag, because it's the thing the single-engine numbers above can hide, is that the noise floor is a property of the engine and the query and the scale together, not a constant you measure once and reuse. A cross-engine cut on the MOAR reference stack made that concrete. Reading the same OCSF 1.5.0 network_activity table on one host, median of four trials with the CV reported next to every latency, the floor moves cell by cell: over a million rows a plain count(*) varies about 10% on DuckDB, 10% on Trino, and 11% on ClickHouse while StarRocks holds it to roughly 1%, a needle lookup on dst_port=3389 sits at 3% / 6% / 8% on the first three and again 1% on StarRocks, the dst_port group-by lands around 7% / 7% / 5% with StarRocks the loose one at 11%, and the high-cardinality distinct over src_ip is the noisiest cell for the embedded-style engines at 14% on DuckDB and 17% on Trino against 6% on ClickHouse and 2% on StarRocks. Push the same four engines a hundredfold to 100 million rows on the same host and the per-engine shape starts to settle rather than read as scatter: the count tightens to 2% on the two engines that were already calm (ClickHouse and StarRocks) while DuckDB loosens to 9% and Trino sits at 4%, and the interesting move is DuckDB's selective point lookup, whose CV climbs from 3% at a million rows to 16% at a hundred million, the noisiest cell anywhere in the run, while the other three hold that lookup in low single digits. The src_ip distinct is where pushing scale becomes its own datapoint, because Trino errored on it outright at 100 million rows and produced no number at all, which is itself a reading about where that engine stops being measurable. Two scale points per engine aren't the full 1M/10M/100M ladder and they aren't a second host, both of which remain the larger open follow-up, but they're enough to settle the direction: no engine is uniformly the calmest, so the CV has to be reported per engine and per query rather than assumed once for the whole test, and quoting any single engine's CV as "the" noise floor would be wrong.

Rule two

Matching a codec is not config parity.

When you compare two formats or two engines, the thing you most want to hold constant is the data, and the trap is assuming that two tools writing "the same data with the same compression" have actually produced comparable files. They haven't. On identical input at the same codec, PyArrow wrote a Parquet file of 193 MB where DuckDB's writer produced 114 MB, a difference of roughly 1.7× from the encoder alone, with nothing changed about the logical rows or the compression algorithm named on the tin. The writer chooses dictionary encoding thresholds, page sizes, row-group boundaries, and how aggressively it applies run-length and delta encodings, and those choices move the file size and the read cost enough to swamp the format-versus-format difference you set out to measure. If you let each engine write its own files and then time the reads, you aren't comparing the formats, you're comparing the writers, and you probably can't tell which.

Config parity, every encoding knob matched across both writers, is the partial fix and it's worth doing, but matching every knob you can name still leaves the ones you can't, so the cleaner answer is to remove the writer from the comparison entirely. Write the data once, then register those same physical bytes into both catalogs. For an Iceberg-versus-DuckLake read comparison this is concrete: you point Iceberg's add_files and DuckLake's ducklake_add_data_files at the byte-identical Parquet, so both catalogs describe the same files on disk and the only thing left varying is the engine's read path. Done that way, Iceberg and DuckLake came out read-neutral on identical bytes, which is the true result, and one you can only see once you've stopped accidentally benchmarking two encoders against each other and calling it a format comparison.

The general form of the rule is that the variable you're testing has to be the only variable that moves, and "same data" is doing a lot of quiet work in that sentence. Same logical rows is not the same as same bytes, and same codec is not the same as same encoding, so when you can arrange to feed both sides the identical files you should, and when you can't you should at least know which uncontrolled knob is carrying your result.

Rule three

Isolate the run, or measure the contention instead.

A timing benchmark has to own the machine while it runs, because anything else heavy on the same host competes for the same cores, the same memory bandwidth, and the same disk queue, and that contention doesn't add a constant you can subtract out. It adds variance, and worse, it adds variance that lands unevenly across the engines depending on when each one happened to be scheduled against the competing load, which is precisely how a benchmark invents a difference that isn't there. I've watched a co-running job inflate the coefficient of variation enough to turn two engines that were genuinely within noise of each other into a clean-looking ranking, and the ranking flipped on the next run because it was never measuring the engines in the first place. It was measuring which one lost the fight for the cache that particular afternoon.

The practice is dull and it works. Run benchmarks one at a time, with the build jobs and the data generation and the other experiments stopped, and don't run a second benchmark in another terminal because you're impatient, which is the version of the mistake I'm most prone to. If you have to share the host, pin the work and account for it honestly rather than pretending the contention washes out, but the default should be a quiet machine. This rule earns its place next to the coefficient of variation because the two failure modes look identical from the outside, both showing up as a spread in the timings, and you'll waste a day chasing an engine difference that was really the compiler you left running in the background.

None of this requires a dedicated benchmarking cluster, which is fortunate because I don't have one. It requires the discipline to let the run have the machine to itself for the few minutes it takes, and to resist parallelizing the one thing whose whole purpose is to be measured serially.

Rule four

Control the environment, including the power plan.

The machine you measure on has settings that move your numbers, and the one that surprised me most was the operating-system power plan. On this Windows host running the benchmarks under WSL2, switching from the default balanced plan to High Performance dropped the coefficient of variation on a sustained workload from 5.8% to 0.8%, and on a short workload from 19.8% to 2.7%. That is not a small effect, and it came from a setting rather than from hardware, no faster CPU, no more memory, no migration to a proper bare-metal box. The balanced plan was throttling the clock between bursts and ramping it back up unevenly, so each run started from a slightly different frequency state and the timings scattered accordingly. Pinning the plan to High Performance kept the clock steady, and the steadier clock made the measurement repeatable.

The general lesson is that "control the environment" means more than closing your browser, because the modern stack is full of adaptive behaviors that trade steady-state performance for power and that stay invisible until you go looking for the variance they cause. CPU frequency scaling, turbo boost, thermal throttling, the laptop-versus-plugged-in governor, the WSL2 memory and CPU caps, the filesystem the temp files land on; any of these can put a wobble in your numbers that you'll misattribute to the engine. The honest move is to fix the ones you can, document the ones you can't, and report the coefficient of variation so the reader sees how steady the platform was while you measured. A benchmark run on an unpinned laptop power plan is reporting the governor's mood as much as the engine's speed.

I'll flag the obvious caveat here because it's the honest one: a setting that cut my variance by an order of magnitude on this host might do less on a server with a fixed clock, and the specific numbers are this machine's. The transferable part is the instruction to go find the adaptive behaviors before they find you, and to treat the environment as something you configured on purpose rather than whatever the laptop happened to be doing.

Rule five

Hash the logical rows, not the bytes.

The last rule is about integrity checking, the part of the harness that confirms two runs operated on the same data, and it has a subtle trap. The obvious way to check that a Parquet file is unchanged is to hash the bytes, and for most file formats that's correct, but Parquet written by a parallel engine is not byte-reproducible by default. Write the same logical table twice with DuckDB and you can get two files whose bytes differ, not because the data differs but because the parallel writer interleaves row groups in whatever order the threads finished, so the row order on disk shifts between runs while every logical row is identical. A byte hash of those two files disagrees, and if your integrity check is a byte hash you'll either get spurious failures or, worse, you'll loosen the check until it stops protecting anything.

The cause is the parallelism, not the writer being wrong, and the fix is to hash what you actually care about, which is the logical content. Compute the hash over the rows in a defined order, or over an order-independent digest of the row set, so two files with the same data and different physical layout hash the same and two files with genuinely different data don't. As a side benefit, if you do want byte-level reproducibility for archival, writing single-threaded or with an explicit ORDER BY gets you there, and the reproducible layout came out roughly 20% smaller in my runs because the sorted order compresses better, so determinism and size pulled in the same direction rather than against each other. That's a pleasant surprise rather than the point, though. The point is that an integrity check has to verify the thing you mean by "same," and for Parquet "same" lives at the level of the rows, not the bytes.

There's a matching risk one layer over, where hashing the logical rows proves two runs saw the same data, but it can't catch a corruption that both runs share, and Parquet's own defense against that, the per-page CRC32 checksum, is honored inconsistently across readers. In a probe where I flipped a single byte inside a checksummed page, chDB verified the checksum and raised an error, while DuckDB and DataFusion don't verify page checksums at all and PyArrow and Polars ship the check turned off by default, so four of the five returned a confident wrong sum. That only bites when a byte actually flips, which makes it an integrity backstop for cheap or cold storage rather than a routine worry, but it makes the same point one layer down: verifying the answer and verifying the bytes are different jobs, and an integrity story for evidence-grade logs wants both, the logical-row hash for run-to-run sameness and the page checksum, where the reader honors it, for a silent corruption underneath.

Put rule zero and rule five together and you have the correctness spine of the whole method: the answer-equality gate checks that two engines computed the same result, and the logical-content hash checks that they computed it over the same data, and between them they close off the two ways a benchmark can be precisely measuring the wrong thing. The speed number sits on top of that spine, and it's only worth reporting because the spine is holding it up.

Why vendor benchmarks are the only benchmarks

The customer who could check the number is contractually locked out.

"You agree not to access or use an Offering to analyze, test, characterize, inspect, or monitor its availability, performance, or functionality for competitive purposes."

Splunk General Terms, Section 1.2(v)

Everything above is about the benchmark you ran yourself, where the only thing standing between you and an honest number is your own discipline. There's a second reason a published benchmark can't be taken on faith, and it has nothing to do with method and everything to do with who is permitted to measure at all. Section 1.2(v) of Splunk's General Terms, the agreement every Splunk Enterprise customer is bound by, prohibits using the product to analyze, test, characterize, inspect, or monitor its performance for competitive purposes, and the Splunk Software License Agreement adds Section 3(f), which prohibits providing benchmark results to third parties without prior written consent. Both clauses bind, and Section 1.2(v) is the broader of the two because it restricts the act of running the comparison and not only the act of publishing the result. The practical effect is that a Splunk customer who wants to evaluate whether ClickHouse, StarRocks, or Trino could run their security analytical workload faster, on their own data, on their own hardware, in their own environment, is contractually prohibited from doing it, and prohibited is the operative word here rather than merely discouraged.

This isn't a quirk of Splunk's contract drafting. Oracle's database licensing has carried a benchmarks clause since the original "DeWitt clause" in the 1980s, named after David DeWitt, the database researcher Oracle prevented from publishing comparative benchmark results, so proprietary data infrastructure has been written this way for forty years. The newer open-platform vendors are the exception worth noting, because Snowflake's Acceptable Use Policy does not restrict benchmark testing and Databricks' Master Cloud Services Agreement carries no comparable competitive-testing clause, which means the schema-on-read SIEM you would be migrating away from is far more likely to bind you than the lakehouse you would be migrating toward. When customers can't legally produce comparative benchmarks against their current platform, the only benchmarks that exist are the ones produced by vendors who never agreed to the bound platform's terms. A startup competing with Splunk publishes "we run security workloads X× faster than Splunk" because the startup never signed Splunk's General Terms, while the Splunk customer who could verify or refute the claim with their own data did sign, and is locked out. So almost every "X% faster than Splunk" benchmark in circulation came from a vendor whose product appears favorably in the result, and the class of benchmark that structurally cannot exist under the current contract regime is the customer-driven head-to-head on real workloads, published openly.

The clearest worked example of how that incentive bends a number comes from outside security, from the streaming-infrastructure market, where the contract regime is looser but the vendor-benchmark dynamic is identical, so the mechanics show through cleanly. Stanislav Kozlovski, an Apache Kafka committer and ex-Confluent engineer, published a forensic read-through in November 2024 of WarpStream's vendor cost calculator, and the calculator, in the version he analyzed, made Kafka look ~3× more expensive than what an experienced operator would actually pay on AWS for the same workload (1 GiB/s sustained, 7-day retention, replication factor 3). The drift came from five mechanical sources, each individually defensible and collectively directional: the calculator selected r4.4xlarge instances when r4.xlarge would have sufficed at the modeled load; it assumed gp2 storage at $0.10/GiB when sc1 HDD at $0.015/GiB was appropriate for the warm-tier workload; it silently shifted the compression-ratio assumption from 5:1 to 4:1 in a quiet update, inflating Kafka costs ~25% with no change to the WarpStream side; it provisioned 60% free space on each broker disk where 50% had been the prior default; and the visible JavaScript multiplied replication cost by an extra (RF-1) factor, double-billing replication traffic at RF=3. Kozlovski's fair AWS Kafka cost for the same workload, before any optimization, came to ~$337k/month against the calculator's $1.264M, a 3.75× delta, and with KIP-405 Tiered Storage and KIP-392 Fetch From Follower the optimized Kafka cost dropped to ~$167k/month, so the headline "WarpStream is 10× cheaper than Kafka" survived in AWS only at 1.3× and reversed sign in GCP (Kafka 21% cheaper) and Azure (Kafka 77% cheaper) at the same workload. Whether WarpStream is a good product is beside the point, because the issue is that the calculator was produced by the vendor who benefits from the comparison, by a methodology the vendor controls, with no independent reviewer who could have pushed back on any of the five drifts before it shipped. None of them required malice, only the ordinary gravity of incentive, and that is exactly the failure mode Section 1.2(v) protects in security, where the customer who could verify can't, the vendor who could be wrong faces no structural check, and the published number becomes the industry reference anyway.

What's instructive is Kozlovski's response, because rather than publish a counter-narrative he shipped a tool, the AKalculator, with the stated principle that it's "incentivized to show the lowest possible cost for Apache Kafka that's also realistic" and the methodology fully disclosed, every assumption visible, every default toggleable, every cost line item attributed to a specific price-sheet entry. The AKalculator's bias is the opposite of the vendor calculator's and he says so on the page, which doesn't make the tool neutral, because nothing is, but it lets the reader adjust, which is the thing a closed methodology cannot offer. Translate the same dynamic to security and the structural problem becomes plain: when the "ClickHouse is X× cheaper than Splunk" benchmark drops, every one of the five Kafka-calculator drift modes is available to the publisher (instance sizing, storage class, compression assumption, free-space allocation, replication accounting) plus the security-specific ones (ingest-source mix, query-suite composition, retention-schema-tax accounting, OCSF normalization cost), the Splunk customer who could run the comparison is contractually prohibited from doing it, and the practitioner who could publish a counter-tool with disclosed methodology faces the same Section 1.2(v) restriction. The fix isn't another counter-benchmark from a differently-incentivized vendor, because what's needed is an independent measurement layer that doesn't share either side's incentive in the first place.

The instinct to build that layer is "open-source the benchmark," push the methodology and the Docker Compose definitions and the data generators to GitHub and let the community verify, and that instinct is right about the methodology, which should be open, but it's wrong about the reference implementation, because the executable artifact, in a comparison set that includes commercial software with restrictive licensing, can't ship publicly without putting either the publisher or the downloader in contract violation. What does work is publishing the methodology, hardware spec, query suite, and result openly under a practitioner brand, while the reference implementation is shared under a one-page mutual NDA with engagement prospects and qualifying reviewers, and the whole thing goes through an annual external review by a named practitioner with relevant standing (a security data engineer, an OCSF contributor, an analyst-firm researcher) under NDA, with their signoff published on the public methodology page. That structure preserves the audit trail that makes a result credible without forcing the publisher to violate the licensing terms of the platforms they're characterizing, and it's close to how the TPC suite and MLPerf already work: both publish methodology openly, neither distributes a turn-key reference implementation a random customer can clone and run against arbitrary commercial software, and both rely on qualified-implementer audit cycles and independent third-party review. Security data has been waiting for the equivalent layer for the last decade and hasn't gotten it, and the contract structure that suppresses it is part of why.

The fourth requirement the TPC analogy understates is the one my own lab embarrassed me into, which is that the benchmark has to verify the answer and not just the clock, because open methodology is no protection against an unverified result and a performance test without a correctness gate can certify the wrong number as a win the moment the wrong engine happens to be the fast one, which is rule zero arriving from the other direction. So a practitioner weighing a benchmark someone hands them has a short checklist: read your own platform's actual contract first, because Splunk's clause is about ten minutes to find at splunk.com/legal and knowing what you can and can't do under your existing license is the precondition for any comparison conversation; discount vendor-published comparative benchmarks accordingly, because "X is 10× faster than your SIEM" reads as a marketing claim rather than a measurement when the customer can't independently verify it; and when an independent benchmark is offered, ask whether the methodology actually publishes openly, whether the result is reproducible by someone running the reference implementation under appropriate licensing, and whether a named external reviewer has audited the methodology under NDA and signed off publicly. Those questions are what separate independent measurement from vendor marketing dressed up as independent measurement, and most published security-tool benchmark coverage I've looked at fails at least one of them.

Sources, all Tier B (Apache Kafka committer + ex-Confluent engineer + tool published with fully disclosed methodology): Stanislav Kozlovski, "The Brutal Truth about Apache Kafka Cost Calculators" (long-form analysis); "no one will tell you the real cost of Kafka" (cross-AZ networking deep dive); AKalculator (the counter-tool with disclosed methodology); KIP-405 (Tiered Storage) and KIP-392 (Fetch From Follower) as the operator-controllable cost-reduction primitives the analysis quantifies. Contract terms quoted verbatim from Splunk General Terms Section 1.2(v) and SLA Section 3(f); Oracle DeWitt-clause history, Snowflake AUP, and Databricks MCSA are public.

When the instrument breaks

The way the instrument breaks is itself a reading.

The rules above are about getting a clean number out of an instrument you trust. There's a harder situation, and it's the one I keep coming back to, where the instrument itself goes blind and the temptation is to treat that as a failure to be scrubbed out rather than as data. I ran into it most clearly on a project that has nothing to do with query engines. I built an index this year, mostly to settle an argument with myself, because every few weeks for two years the AI discourse has announced with total confidence that everything just changed, and I wanted to know whether everything actually kept changing or whether I was only marinating in the announcements, so I went looking for a hard version of the question in Google Trends. The plan was simple, pull the search interest for the major models and the major moments and see whether the public's curiosity followed the cycle, and the plan did not survive contact with the data, in five distinct ways that turned out to be more interesting than the chart I set out to draw.

The first thing that broke was the baseline, because "ChatGPT" has quietly become the word people use when they mean AI at all, the way "Google" became the verb for search, so the curve is an ambient hum of general awareness rather than a measurement of OpenAI's mindshare, and it sits loud enough to drown every other signal until you subtract it out as a noise floor. The second was comparability: Google keeps clean entities for the OpenAI versions, so GPT-4 and GPT-4o and GPT-5 each read cleanly, but it doesn't for Claude or Gemini, where only the literal string is available, catching a fraction of the real interest, so putting the two together compares a calibrated gauge against a bent one and the bent one always reads low. The third was how small the serious signal is, because the people who search a specific model number are a minority of a minority against the generic hum, the entire model-number field running several times smaller than the bare "ChatGPT" stream and the gap widening toward twenty-fold against any single flagship term. The fourth is the one that reorganized the whole exercise, because Google Trends measures Google search, and the two groups closest to these models are exactly the two it can't see: the developers who ask Claude about Claude inside the tool rather than typing the question into a search bar (measured on OpenRouter, where developers route their traffic, Claude served the majority of programming tokens through most of 2025), and the Chinese users whose interest in DeepSeek or Qwen is invisible because Google holds on the order of 3% of mainland search, where Baidu dominates. The fifth came with a number attached, because ordinary information-seeking has been migrating off the search box and onto the models themselves, with Gartner forecasting traditional search volume falling roughly a quarter by 2026 as answer-engines absorb the queries.

The undercount is not noise to be scrubbed out of the chart, because the undercount is the chart: the instrument is going blind in precisely the direction the technology is moving, so the blind spot itself is a reading, and the harder a population is to see in Google Trends the more completely that population has already left for the new way of working. The developers and the Chinese users are faint not because they are few but because they got there first. I set out to measure the migration to AI, and the cleanest evidence of the migration was that my instrument for measuring it had stopped working in exactly the places the migration had already finished, which is to say you cannot measure this shift with the old ruler because the shift is what is bending the ruler. I did eventually force a defensible picture out of it, baseline removed and the developer and Chinese signals lifted back toward where the platform data says they belong, but I hold that corrected chart loosely and label it an estimate stitched from documented adjustments, because the honest output of the effort was never the chart but the realization about the ruler.

This is not really a story about AI, and the reason it belongs in a benchmark essay is that it's the same discipline one layer up from rule zero. In security data work the instrument is the telemetry, and the standing temptation is to mistake what the instrument can see for what is there: you measure the threat by what lands in the SIEM and quietly conclude the threat looks like your log sources, you measure tool adoption by who answered the survey and conclude the field looks like people who answer surveys, you measure detection coverage by the rules you wrote and the attacks you have no rule for become, by definition, attacks that are not happening. Each of those is the Google Trends problem in a different uniform, a signal only ever as good as the platform it was collected on, and a platform that silently encodes who is in the audience and who never shows up. So the discipline is to hold two questions about every signal at once, what is this actually measuring and who or what is structurally invisible to it, and then the harder move the hype index forced on me, to read the blind spot as data in its own right, because a population you cannot see is telling you something by being unseeable. The most expensive mistakes I've watched security teams make were not made on bad data, they were made on good data from an instrument nobody had asked the second question about, data that was accurate and complete about a world slightly narrower than the one the team actually lived in. When the ruler bends, the bend is the reading, and it's worth paying attention to which way it bends, because that is where everything actually changed.

Evidence: Tier C/D for the hype-index construction (first-party Google Trends pulls plus hand-counted discourse, explicitly held as an estimate stitched from documented adjustments, not a clean measurement); Tier B for the supporting platform signals (OpenRouter developer-routing share, Baidu's mainland search dominance) and Tier B for the Gartner forecast that traditional search volume falls roughly a quarter by 2026. The shape of the cycle is the Gartner Hype Cycle, borrowed not discovered. The transferable claim, that a blind spot in an instrument is a reading about where the territory has already moved, is the part this essay stands behind.

What this is and isn't

One machine, Tier B, and the method is the part that travels.

I should be precise about what these results are, because the discipline I'm arguing for applies to me first. This is single-machine, first-party, Tier B work. The absolute milliseconds are this host's and nobody else's, the 1.7× PyArrow-versus-DuckDB file-size gap is from these particular writer defaults at these versions, and the power-plan variance numbers are a property of this Windows and WSL2 setup. I'd push back on anyone, including me, who quoted the raw numbers as a server-class result or a universal constant. What transfers is not the milliseconds but the ratios and, more durably, the method: the coefficient of variation has a scale below which your comparison is noise, encoders make "same codec" an incomplete control, contention inflates variance, adaptive power behaviors wobble an unpinned clock, and parallel writers make byte hashes the wrong integrity check for Parquet. Those are properties of the measurement problem, and they hold whatever host you run on.

The closest analogues for what a credible benchmark looks like come from outside security, in the TPC suite and in MLPerf, both of which publish their methodology openly and subject results to audited review rather than asking you to take a vendor's table on faith. Security data has lacked the equivalent, an independent layer with open methodology and a named reviewer, and as the contract section above lays out the reasons it's lacked one are partly contractual, because the schema-on-read SIEMs prohibit customers from running competitive performance tests at all, so the only benchmarks that exist are vendor-funded by construction. The method rules are the other half of that argument, because even when no contract is stopping you, even on open engines you ran yourself, the benchmark will still mislead you if it skips rule zero, since open methodology you can read is no protection against an answer nobody verified.

So the method is the deliverable, more than any single milliseconds figure, and that's deliberate. A speed number ages the moment the next engine version ships, but a harness that gates on correctness, reports its noise, controls its data and its environment, and hashes the right thing keeps producing numbers you can stand behind across versions and hosts. The numbers are evidence; the method is what makes them admissible.

Evidence: Tier B (first-party, single machine, reproduced). Findings drawn from SDW Lab runs on a Windows/WSL2 host: the cross-engine answer-equality divergence (chDB 4.1.8 vs DuckDB over byte-identical Parquet); coefficient of variation up to 55% at 1M rows settling to ~5% at 10M and ~4% at 100M; PyArrow 193 MB vs DuckDB 114 MB at the same codec on identical data; the High-Performance power plan dropping CV from 5.8% to 0.8% (sustained) and 19.8% to 2.7% (short); and Parquet's lack of default byte-reproducibility from parallel row order, with the reproducible single-threaded / ORDER BY layout running roughly 20% smaller. Absolute timings are this host's; the ratios and the method transfer. Methodology is published with the lab.

A number you can stand behind, or a number that just came out.

The difference between the two isn't a faster machine, it's a harness that checks the answer before the clock, reports its own noise, feeds both sides the identical bytes, owns the host while it runs, and hashes the rows instead of the file. The lab publishes the method openly and gates the reference implementation, so the result is reproducible without anyone violating a license.