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").