Benchmarking LabA field guide to performanceSource ↗
Chapter 03Working labAbout 30 minutes

Are they doing the same work?

A fast answer helps the customer only if it answers the question they asked. You will run a deliberately flawed comparison, catch the mistake with a small independent reference, and repair it before interpreting the clock.

Chapter 2 gave both engines the same rows and checked their answers. Now the sales operations team asks for January revenue and the number of orders in each region. A colleague has a shorter query that appears to do the job. Your task is to establish whether its result belongs in the comparison at all.

Your evidence: a written result contract → an edge-case fixture → a real SQL run → a pass or fail on every answer → a corrected comparison record.

01 / Define success first

Write the answer before timing it

A comparison contract is a short agreement about the work each option must complete. It names the input, output, resources, permitted tuning and guarantees. “Run a revenue query” is too loose: different month boundaries, missing amounts and duplicate values can all change the answer.

For this exercise, January means month_id = 1. Every row is a separate order. An unknown amount, represented by SQL NULL, still counts as an order and contributes zero cents to the total. Repeated amounts remain separate purchases. Return the region, order count and revenue in integer cents; sort the result by region. Integer cents keep this example free of currency rounding differences.

The contract for this customer question
Condition Required in this exercise
Input The same eight orders, including every edge case
Output January only; all orders counted; revenue summed by region
Resources One connection and one execution thread per engine
Tuning policy Both unindexed, or the same month index statement on both
Timing boundary Send SQL through receipt of all ordered string rows
Guarantees tested Correct reads from a local in-memory database

An index is an extra structure that can help find selected rows. Building it costs time and memory. Equal index statements are a documented tuning policy; they do not force the engines to use identical physical plans. A different customer question may permit each option's best supported configuration under equal cost or resource limits. Choose that policy before seeing the winner.

02 / A worked example

Three small changes, one wrong report

Our eight orders include two separate purchases for 100 cents, an unknown amount, a zero amount, a refund of −100 cents, and rows outside January. An edge case is an input that tests a boundary or an unusual but permitted value. Small fixtures make mistakes easy to inspect before scaling up.

The complete fixture; row identity is the order ID
Order Month Region Cents
1 1 1 100
2 1 1 100
3 1 2 NULL
4 1 2 0
5 2 1 700
6 2 1 −100
7 12 2 900
8 1 1 250

Work through January by hand. Region 1 has orders 1, 2 and 8: 3 orders, 450 cents. Region 2 has orders 3 and 4: 2 orders, 0 cents. Those two rows are our expected answer, computed independently of either database.

-- The colleague's candidate
SELECT region_id, COUNT(amount_cents),
       COALESCE(SUM(DISTINCT amount_cents), 0)
FROM orders
WHERE month_id <= 2
GROUP BY region_id ORDER BY region_id;

COUNT(amount_cents) counts known amounts, so it omits the unknown one. DISTINCT removes repeated monetary values before adding, even when they belong to separate orders. The month condition includes February. COALESCE(value, 0) substitutes zero when an aggregate is unknown; it does not repair the other errors.

The candidate returns region 1 as 5 orders and 950 cents, and region 2 as 1 order and 0 cents. A report can therefore look plausible and still answer another question. The correct query uses COUNT(*), ordinary SUM(amount_cents), and month_id = 1.

03 / Execute the contract

Catch the wrong work, then repair it

Run the candidate first. The result should fail in both engines regardless of which takes less time. Then select the repaired query and run again. Optionally add the month index to both options and inspect its preparation time separately. Predicting is optional; you can also read the complete SQL before running.

Inspect the complete SQL and expected rows
CREATE TABLE orders (order_id INTEGER, month_id INTEGER,
 region_id INTEGER, amount_cents INTEGER);
-- All eight rows are shown above.
-- Expected rows: [1, 3, 450], [2, 2, 0].
-- The repaired query counts every row and filters month_id = 1.

Ready. Engines load only when you press Run. No timings have been recorded.

Real DuckDB-Wasm 1.32.0 and PGlite 0.5.13 package builds run locally in fresh workers. First use can load about 54 MiB. Setup runs DuckDB first; three candidate requests per engine alternate which engine goes first. These include first-use effects; this is a correctness exercise, with no separate query warm-up. Keep the tab visible.

The download keeps the completed configuration, exact SQL, full fixture, expected and actual rows, runtime versions, asset hashes, raw times and your current notes. A failed check is retained visibly as a failed check. Completed records stay in this tab only until the next run or page exit; download before rerunning.

04 / Read the evidence

Correctness is an admission rule

A failed answer is excluded from a speed claim about the agreed work. It is not a slower answer with a penalty attached: it is different work. The repaired run supports only the stated fixture and contract. Eight rows are enough to expose these defects; they are too few to evaluate an index's performance value or realistic query scaling.

Equivalent result

The same ordered values under the same meaning of missing data, duplicates, units and boundaries. Different SQL can satisfy one contract.

Declared tuning

Report index build and load costs, memory, defaults and permitted changes. An index advantage can be relevant when its cost is allowed.

Equivalent promises

Durability means acknowledged changes survive a stated failure. An in-memory read exercise supplies no crash-survival evidence.

For a server purchase, ask when a write is acknowledged, how replicas participate, what consistency a reader sees, and which failure the design must survive. Two systems can return the same rows today while offering different recovery behaviour. Test those guarantees in the intended deployment before using performance to choose between them.

Hint 1 · I see a mismatch. Where should I start?

Check the expected January rows against the eight input rows. Region 2 is useful: an unknown amount belongs in its order count.

Hint 2 · Why is region 1 also wrong?

The candidate includes February. It also merges the two 100-cent values before adding them. Removing February alone leaves a 350-cent January total.

Hint 3 · What is the complete repair?

Use COUNT(*), SUM(amount_cents) without DISTINCT, and WHERE month_id = 1. Keep the sort and zero substitution. Choose the repaired query to execute that change in both engines.

05 / Independent practice

Design an adversarial fixture

Your next customer asks for “orders paid in January.” Before adding a timing chart to a proposal, write a five-line contract and invent three rows that would reveal an incorrect implementation. Choose at least one case absent from this fixture: an empty month, a missing region, two currencies, a timestamp at midnight, or repeated order identities. State the required handling before calling either query correct.

  1. Download the failing record and repaired record; explain the exact answer difference.
  2. Write expected rows for your three new inputs by hand. Explain why each row could change the recommendation.
  3. Choose a tuning policy and a required service guarantee. Separate a directly measured fact from a guarantee this browser cannot establish.

To take the next programming step, open the source and change EDGE_ROWS in src/methods-model.ts, then update fairReference() if your contract changes. A reference is a simple independent implementation of the expected result. Keep it readable enough to review by hand. Follow the repository's reproduction instructions and run your edited fixture locally. For timestamps or currency, extend the schema and both SQL and reference; changing just the displayed labels does not test that contract.

Draft and selected controls save on this device when local storage is available. Notes join the completed evidence at download time.

Check your work

A colleague should be able to compute your expected result without running either engine. Your speed claim should use a passing record, name the allowed tuning, and avoid implying that the browser tested persistence or recovery.

06 / Sources and reproducibility

Make the contract inspectable

The exercise uses the same pinned local engine resources as Chapter 2. Request timing includes dispatch, worker scheduling, engine execution, conversion to plain string rows and response delivery. Answer comparison happens after the clock. Startup, loading, optional index creation and ANALYZE (gathering query-planner statistics) are recorded separately. Stop, a hidden tab, worker errors or request time limits leave an incomplete record with its error visible.

Next, keep the answer contract and ask whether these inputs resemble the customer's real work.