Files

3.6 KiB

6. Why this module's work is CPU-bound, not I/O-bound

← Previous | README | Next: The RejectedExecutionException →

The design choice

RiskScoringProcessor does risk.iterations (150 by default) real 64-bit XOR/multiply operations per order, then flags the order high-risk if the resulting score exceeds a threshold or the amount exceeds risk.high-risk-amount-cents. No network call, no sleep, no artificial delay — every millisecond it spends is spent computing, on whichever core its thread is scheduled on.

That is deliberate. A partitioning demo built around an I/O-bound step (a network call per item, a slow downstream service) shows a speedup even on a single-core machine, because the threads spend almost all their time blocked, not competing for CPU — the "parallelism" it demonstrates is really just concurrency hiding latency, which is a real and useful thing but a different claim than "this uses more of the machine's compute." A CPU-bound processor makes the speedup measured in docs/output/09-full-scale-throughput.txt mean what it looks like it means: more cores actually doing more arithmetic per second, capped by how many cores physically exist.

Determinism, and what it buys

RiskScoringProcessorTest pins that the same Order always produces the same score:

order: Order[orderId=42, customerId=777, amountCents=1234567, region=NORTH]
first.process()  -> riskScore=61 highRisk=false
second.process() -> riskScore=61 highRisk=false

Full transcript: docs/output/01-processor-determinism.txt.

Determinism matters for a partitioned job specifically because of chapter 8: a partition that fails and re-runs must produce the same output the second time, or a restart silently changes results depending on which attempt happened to write. MERGE ... KEY(order_id) in the writer (chapter 4) handles the "don't duplicate the row" half of that; a deterministic processor handles the "don't change the row's content between attempts" half.

What the CPU cost does and does not explain about the scaling numbers

Raising risk.iterations to 5000 (thirty-three times the default) at a fixed 300,000-row data volume moved the grid-size-2 speedup from 1.10x to 1.26x — see docs/output/09-full-scale-throughput.txt for both numbers side by side. That is evidence that at least part of the shortfall from a clean 2x speedup on 2 cores is time spent somewhere that does not scale with thread count — the shared H2 writer is the leading candidate, discussed further in chapter 10 — but it is not proof by itself; this module did not isolate the writer completely (by, say, writing to per-partition tables and comparing) to rule out GC pressure or thread scheduling overhead as contributing causes too. Read the 1.10x-to-1.26x shift as "consistent with the write-contention hypothesis," not as a closed case.

Going deeper

  • Amdahl's law, for the general shape of "some fraction of the work cannot be parallelized": Wikipedia — Amdahl's law (rel="nofollow").

Next: The RejectedExecutionException →