How do commitCount and rollbackCount relate to chunks and transactions, and what makes rollbackCount rise beyond a single failure?
answer
- chunk = one transaction
- commitCount ≈ ceil(items / chunkSize)
- rollback + single-item re-scan to isolate bad item
- retry re-runs chunk => more rollbacks
- commit/rollback tracked on StepExecution directly
basics
~20 sEach chunk runs in one transaction. commitCount increments per successful chunk commit (roughly items/chunkSize). rollbackCount increments each time a chunk transaction rolls back — including the extra rollbacks caused by retry and by single-item re-scanning during skips.
solid answer
~40 sIn a chunk-oriented step, Spring Batch wraps each chunk in a transaction. A clean chunk commits and increments commitCount, so with N items and chunk size C you expect about ceil(N/C) commits when everything succeeds. rollbackCount increments whenever a chunk's transaction rolls back. In a fault-tolerant step a single bad item can cause several rollbacks: on a write exception the framework rolls back, then re-processes the chunk one item at a time to isolate the offender, and each retry/scan attempt that fails rolls back again. Retries (retry(...).retryLimit()) similarly roll back and re-run the chunk. So rollbackCount can be much larger than the number of distinct failing items. commitCount and rollbackCount are tracked directly on the StepExecution (not via StepContribution), because they describe transaction outcomes rather than per-item deltas.
code
java · 10 linesStep step = new StepBuilder("load", jobRepository)
.<Row, Row>chunk(100, txManager) // 100-item chunk = 1 transaction
.reader(reader).writer(writer)
.faultTolerant()
.skip(DataIntegrityViolationException.class).skipLimit(5)
.retry(TransientDataAccessException.class).retryLimit(3)
.build();
// One bad write in a 100-item chunk -> rollback, then the chunk is
// re-scanned item-by-item to find it: rollbackCount can jump by many,
// while commitCount only counts the chunks that ultimately commit.go deeper
Know one chunk = one transaction; commit on success, rollback on failure.
Estimate commitCount as items/chunkSize and know rollback happens on chunk failure.
Explain skip-scanning and retry inflating rollbackCount, and COMPLETED-with-rollbacks being normal.
Use commit/rollback ratios for operational alerting and reason about chunk-size trade-offs on skip-scan cost.
### Chunk = transaction A chunk-oriented step reads `chunkSize` items, processes them, hands the chunk to the writer, and **commits one transaction** per chunk. The `PlatformTransactionManager` you pass to the `StepBuilder.chunk(size, txManager)` governs this boundary. ### commitCount Incremented **once per successfully committed chunk**. For `N` items and chunk size `C`, a fully successful run yields roughly `ceil(N/C)` commits. (There can be an extra empty/last transaction depending on reader exhaustion.) It is a proxy for 'how many chunk transactions succeeded', not an item count. ### rollbackCount — the interesting one Incremented **each time a chunk transaction rolls back**. In a **non**-fault-tolerant step, the first exception rolls back once and the step fails, so rollbackCount is small. In a **fault-tolerant** step it can climb well past the number of bad items, for two reasons: 1. **Skip scanning.** Spring Batch buffers a chunk's items. If the **writer** throws (it can't tell which item is bad because writes are often batched), the framework rolls back and **re-processes the chunk one item at a time** to isolate the offending item. Each of those failing single-item attempts is another rollback. So one bad row in a chunk of 100 can produce many rollbacks. 2. **Retry.** With `faultTolerant().retry(SomeTransient.class).retryLimit(k)`, a transient failure re-runs the chunk up to `k` times; every failed attempt rolls back. ### Why they live on StepExecution, not StepContribution Read/write/filter/skip deltas are staged in a per-chunk `StepContribution` and applied on commit. But **commit/rollback counts describe the fate of the transaction itself**, so the framework increments them **directly** on the shared `StepExecution` (`incrementCommitCount()` after commit; rollback count on rollback). They are therefore not part of `apply()`. ### Practical reading - A high `rollbackCount` relative to `commitCount` signals lots of skip-scanning or retrying — usually a data-quality or transient-infra problem worth alerting on. - `rollbackCount > 0` with `status = COMPLETED` is normal for fault-tolerant steps: it rolled back, recovered via skip/retry, and finished. - In a **tasklet step**, the tasklet body runs in a transaction; a failure/retry there also affects rollbackCount, but read/write/filter are usually 0. ### Gotchas - Don't equate rollbackCount with 'number of failed items' — scanning and retries inflate it. - Larger chunk sizes make skip-scanning more expensive (more single-item re-reads per bad row), which shows up as higher rollbackCount. - commitCount depends on chunk size, so comparing it across runs with different chunk sizes is apples-to-oranges.
- Can a step have status COMPLETED but a non-zero rollbackCount?Yes. In a fault-tolerant step a chunk can roll back, then recover via skip or retry and eventually commit. The rollbacks are recorded in rollbackCount even though the step completes successfully.
- Why can one bad item produce many rollbacks?Because writes are batched, the framework can't tell which item failed. It rolls back and re-reads the chunk one item at a time to isolate the offender; each failing single-item attempt is another rollback. Retry limits add still more.
saying these in an interview costs you the question
- Equating rollbackCount with the number of failed items
- Thinking commitCount counts items rather than chunk transactions
- Believing a non-zero rollbackCount means the step failed