62 lines
3.6 KiB
Markdown
62 lines
3.6 KiB
Markdown
# 6. Why this module's work is CPU-bound, not I/O-bound
|
|
|
|
[← Previous](05-the-diagnostic-endpoint.md) | [README](../README.md) | [Next: The RejectedExecutionException →](07-the-rejectedexecutionexception.md)
|
|
|
|
## The design choice
|
|
|
|
[`RiskScoringProcessor`](../src/main/java/com/ankurm/batchpartition/processing/RiskScoringProcessor.java)
|
|
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`](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`](../src/test/java/com/ankurm/batchpartition/processing/RiskScoringProcessorTest.java)
|
|
pins that the same `Order` always produces the same score:
|
|
|
|
```console
|
|
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`](output/01-processor-determinism.txt).
|
|
|
|
Determinism matters for a partitioned job specifically because of
|
|
[chapter 8](08-restart-reruns-only-the-failed-partition.md): 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](04-the-writer-and-the-beanmapped-trap.md)) 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`](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](10-scaling-sensitivity-to-data-size.md) — 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](https://en.wikipedia.org/wiki/Amdahl%27s_law) (`rel="nofollow"`).
|
|
|
|
[Next: The RejectedExecutionException →](07-the-rejectedexecutionexception.md)
|