本文
自分が超絶誤解していたので、メモしておきます(´・ω・`)
上記ドキュメントにすべて語られている通りなのですが、chunk内でのItemReader→ItemProcessor→ItemWriterの呼び出しは、以下のようになります (疑似コード)
var items = []
CHUNK_SIZE.times do
item = ItemReader.read()
items.push(item)
end
var processedItems = []
for item in items
processedIem = ItemProcessor.process(item)
processed.push(processedItem)
end
ItemWriter.write(processedItems)
Commit
注意しないといけないのは、チャンクサイズ分のデータをメモリに展開する可能性があるということです。
たとえば、csvファイルを1行ずつ読み込み、Order(注文)クラスにマッピングするItemReaderがあるとします。
@Bean
public ItemReader<Order> sampleReader() {
return new FlatFileItemReaderBuilder<Order>()
.name("sampleReader")
.resource(new PathResource("input.csv"))
.delimited()
.delimiter(",")
.names("id", "name", "price")
.targetType(Order.class)
.build();
}
このときSpring Batchはチャンクサイズ行分のcsvを読み込もうとします。たとえば、このItemReaderを利用するステップが以下のように定義されていたら、csvの1000行分がメモリに展開される可能性があります。
@Bean
public Step sampleStep(
JobRepository jobRepository,
PlatformTransactionManager platformTransactionManager,
ItemReader<Order> sampleReader,
ItemProcessor<Order, Order> sampleProcessor,
ItemWriter<Order> sampleWriter) {
return new StepBuilder("sampleStep", jobRepository)
.<Order, Order>chunk(1000, platformTransactionManager)
.reader(sampleReader)
.processor(sampleProcessor)
.writer(sampleWriter)
.build();
}
チャンクサイズが大きく設定すると、メモリに展開されるデータ量が多くなり、それだけメモリ量を消費します。わたしはこの仕様を勘違いしており、チャンクサイズを適当に設定した結果、Out Of Memoryを発生させました…
また、チャンクがトランザクションの単位であることにも注意が必要です。上記の例でいえば、sampleWriterでデータベース更新を行っているとすると、1000件ごとにコミットが走ります。
チャンクサイズが小さいと、コミット処理が何度も走り、処理が遅くなったり、途中でエラーが発生した場合にデータベースのデータが妙な状態になったりする可能性が高まります。一方、チャンクサイズが大きいと、なかなかコミットされないため、テーブルをロックしてしまったり、データベースのTEMP領域をやたらに消費したりします。
まとめ
上記のような仕様を理解したうえで、チャンクサイズは慎重に設定する必要があります。処理対象のデータ量、利用しているリソース(メモリやDBの特性)などを加味して、最適なサイズを検討することが重要です。
環境情報
- Spring Boot 3.3.1
- Spring Batch 5.1.2