65 lines
3.4 KiB
Markdown
65 lines
3.4 KiB
Markdown
# 5. The diagnostic endpoint: proving four threads ran, not just configuring them
|
|
|
|
[← Previous](04-the-writer-and-the-beanmapped-trap.md) | [README](../README.md) | [Next: Why CPU-bound, not I/O-bound →](06-why-cpu-bound-not-io-bound.md)
|
|
|
|
Configuration says how many partitions *should* run and on how many threads. It does not say what
|
|
actually happened on a given execution — whether the pool was saturated, whether two
|
|
partitions landed on the same thread, how long each one actually took relative to the others. This
|
|
module makes that observable instead of assumed.
|
|
|
|
## PartitionStatsListener
|
|
|
|
[`PartitionStatsListener`](../src/main/java/com/ankurm/batchpartition/partition/PartitionStatsListener.java)
|
|
is a `StepExecutionListener` attached to the worker step. `beforeStep` records a start timestamp on
|
|
a `ThreadLocal` (each partition runs on its own thread, so there is no cross-talk); `afterStep`
|
|
computes the duration and inserts one row into `PARTITION_STATS`: which step (which partition),
|
|
which OS thread name, how many rows it read, when it started and finished, how long it took, and
|
|
its exit code.
|
|
|
|
## PartitionInsightController
|
|
|
|
[`PartitionInsightController`](../src/main/java/com/ankurm/batchpartition/web/PartitionInsightController.java)
|
|
exposes that table over HTTP:
|
|
|
|
```console
|
|
$ curl -s http://localhost:8081/batch/partitions/1 | python3 -m json.tool
|
|
[
|
|
{
|
|
"PARTITION_NAME": "ordersWorkerStep:partition0",
|
|
"THREAD_NAME": "order-partition-2",
|
|
"READ_COUNT": 5000,
|
|
"STARTED_AT": "2026-09-14T09:29:56.892Z",
|
|
"FINISHED_AT": "2026-09-14T09:29:57.968Z",
|
|
"DURATION_MS": 1075,
|
|
"EXIT_CODE": "COMPLETED"
|
|
},
|
|
...
|
|
]
|
|
```
|
|
|
|
Full transcript, all four partitions: [`docs/output/05-happy-path-4-partitions.txt`](output/05-happy-path-4-partitions.txt).
|
|
Two things this makes concrete that configuration alone does not: partition-to-thread assignment
|
|
is not 1:1 with partition *number* (`partition0` landed on thread `order-partition-2` here, not
|
|
`order-partition-1` — `TaskExecutorPartitionHandler` submits tasks in the order the
|
|
`Partitioner`'s map iterates, and `ThreadPoolTaskExecutor` hands them to whichever pooled thread is
|
|
free first); and all four really did start within milliseconds of each other, which is the
|
|
difference between "partitioned" as a configuration property and "partitioned" as an observed
|
|
fact.
|
|
|
|
This same table is what makes [chapter 7](07-the-rejectedexecutionexception.md) and
|
|
[chapter 8](08-restart-reruns-only-the-failed-partition.md) possible to write with confidence:
|
|
querying `PARTITION_STATS` (or, for those chapters, the framework's own `BATCH_STEP_EXECUTION`
|
|
table directly) is how this article found that three rejected partitions do not fail, they hang.
|
|
|
|
**Delete this before shipping.** An unauthenticated endpoint that dumps job-internal timing data
|
|
belongs behind whatever authentication and authorization the rest of the service already has, at
|
|
minimum, and arguably behind nothing publicly reachable at all — it exists here to make the
|
|
mechanism visible for this article, not because a production batch service should expose it.
|
|
|
|
## Going deeper
|
|
|
|
- `StepExecutionListener` and the other listener interfaces available:
|
|
[Spring Batch reference — Listeners](https://docs.spring.io/spring-batch/reference/step/chunk-oriented-processing/configuring-a-step.html) (`rel="nofollow"`).
|
|
|
|
[Next: Why CPU-bound, not I/O-bound →](06-why-cpu-bound-not-io-bound.md)
|