diff --git a/spring-batch-docs/asciidoc/scalability.adoc b/spring-batch-docs/asciidoc/scalability.adoc index 9ea57eb04..29050b670 100644 --- a/spring-batch-docs/asciidoc/scalability.adoc +++ b/spring-batch-docs/asciidoc/scalability.adoc @@ -6,6 +6,8 @@ == Scaling and Parallel Processing +include::toggle.adoc[] + Many batch processing problems can be solved with single threaded, single process jobs, so it is always a good idea to properly check if that meets your needs before thinking about more complex implementations. Measure the performance of a realistic job and see if @@ -34,16 +36,42 @@ First, we review the single-process options. Then we review the multi-process op === Multi-threaded Step The simplest way to start parallel processing is to add a `TaskExecutor` to your Step -configuration. For example, you might add an attribute of the `tasklet`, as shown in the +configuration. + +[role="xmlContent"] +For example, you might add an attribute of the `tasklet`, as shown in the following example: -[source, xml] +[source, xml, role="xmlContent"] ---- ... ---- +[role="javaContent"] +When using java configuration, a `TaskExecutor` can be added to the step +as shown in the following example: + +.Java Configuration +[source, java, role="javaContent"] +---- +@Bean +public TaskExecutor taskExecutor(){ + return new SimpleAsyncTaskExecutor("spring_batch"); +} + +@Bean +public Step sampleStep(TaskExecutor taskExecutor) { + return this.stepBuilderFactory.get("sampleStep") + .chunk(10) + .reader(itemReader()) + .writer(itemWriter()) + .taskExecutor(taskExecutor()) + .build(); +} +---- + In this example, the taskExecutor is a reference to another bean definition that implements the `TaskExecutor` interface. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/task/TaskExecutor.html[`TaskExecutor`] @@ -57,10 +85,12 @@ Note that this means there is no fixed order for the items to be processed, and might contain items that are non-consecutive compared to the single-threaded case. In addition to any limits placed by the task executor (such as whether it is backed by a thread pool), there is a throttle limit in the tasklet configuration which defaults to 4. -You may need to increase this to ensure that a thread pool is fully utilized, as shown in -the following example: +You may need to increase this to ensure that a thread pool is fully utilized. -[source, xml] +[role="xmlContent"] +For example you might increase threads, as shown in the following example: + +[source, xml, role="xmlContent"] ---- ---- +[role="javaContent"] +When using java configuration, threads can be increased on the `TaskExecutor`, +as shown in the following example: + +.Java Configuration +[source, java, role="javaContent"] +---- +@Bean +public TaskExecutor taskExecutor(){ + SimpleAsyncTaskExecutor asyncTaskExecutor=new SimpleAsyncTaskExecutor("spring_batch"); + asyncTaskExecutor.setConcurrencyLimit(20); + return asyncTaskExecutor; +} +---- + Note also that there may be limits placed on concurrency by any pooled resources used in your step, such as a `DataSource`. Be sure to make the pool in those resources at least as large as the desired number of concurrent threads in the step. @@ -100,11 +145,13 @@ single threaded configuration. As long as the application logic that needs to be parallelized can be split into distinct responsibilities and assigned to individual steps, then it can be parallelized in a -single process. Parallel Step execution is easy to configure and use, for example, to -execute steps `(step1,step2)` in parallel with `step3`, as shown in the following -example: +single process. Parallel Step execution is easy to configure and use. -[source, xml] +[role="xmlContent"] +For example, executing steps `(step1,step2)` in parallel with `step3` is straightforward, +as shown in the following example: + +[source, xml, role="xmlContent"] ---- @@ -122,6 +169,51 @@ example: ---- +[role="javaContent"] +When using java configuration, executing steps `(step1,step2)` in parallel with `step3` +is straightforward, as shown in the following example: + +.Java Configuration +[source, java, role="javaContent"] +---- +@Bean +public Job job() { + return jobBuilderFactory.get("job") + .start(splitFlow()) + .next(step4()) + .build() //builds FlowJobBuilder instance + .build(); //builds Job instance +} + +@Bean +public Flow splitFlow() { + return new FlowBuilder("splitFlow") + .split(taskExecutor()) + .add(flow1(), flow2()) + .build(); +} + +@Bean +public Flow flow1() { + return new FlowBuilder("flow1") + .start(step1()) + .next(step2()) + .build(); +} + +@Bean +public Flow flow2() { + return new FlowBuilder("flow2") + .start(step3()) + .build(); +} + +@Bean +public TaskExecutor taskExecutor(){ + return new SimpleAsyncTaskExecutor("spring_batch"); +} +---- + The configurable "task-executor" attribute is used to specify which `TaskExecutor` implementation should be used to execute the individual flows. The default is `SyncTaskExecutor`, but an asynchronous `TaskExecutor` is required to run the steps in @@ -191,9 +283,12 @@ image::{batch-asciidoc}images/partitioning-spi.png[Partitioning SPI, scaledwidth The `Step` on the right in this case is the "remote" slave, so, potentially, there are many objects and or processes playing this role, and the `PartitionStep` is shown driving -the execution. The following example shows the `PartitionStep` configuration: +the execution. -[source, xml] +[role="xmlContent"] +The following example shows the `PartitionStep` configuration: + +[source, xml, role="xmlContent"] ---- @@ -202,6 +297,23 @@ the execution. The following example shows the `PartitionStep` configuration: ---- +[role="javaContent"] +The following example shows the `PartitionStep` configuration using java configuration: + +.Java Configuration +[source, java, role="javaContent"] +---- +@Bean +public Step step1Master() { + return stepBuilderFactory.get("step1.master") + .partitioner("step1", partitioner()) + .step(step1()) + .gridSize(10) + .taskExecutor(taskExecutor()) + .build(); +} +---- + Similar to the multi-threaded step's `throttle-limit` attribute, the `grid-size` attribute prevents the task executor from being saturated with requests from a single step. @@ -237,11 +349,14 @@ or remoting fabrics. Spring Batch does, however, provide a useful implementation of `PartitionHandler` that executes `Step` instances locally in separate threads of execution, using the `TaskExecutor` strategy from Spring. The implementation is called -`TaskExecutorPartitionHandler`, and it is the default for a step configured with the XML +`TaskExecutorPartitionHandler`. + +[role="xmlContent"] +The `TaskExecutorPartitionHandler` is the default for a step configured with the XML namespace shown previously. It can also be configured explicitly, as shown in the following example: -[source, xml] +[source, xml, role="xmlContent"] ---- @@ -254,6 +369,31 @@ following example: ---- +[role="javaContent"] +The `TaskExecutorPartitionHandler` can be configured explicitly within java configuration, +as shown in the following example: + +.Java Configuration +[source, java, role="javaContent"] +---- +@Bean +public Step step1Master() { + return stepBuilderFactory.get("step1.master") + .partitioner("step1", partitioner()) + .partitionHandler(partitionHandler()) + .build(); +} + +@Bean +public PartitionHandler partitionHandler() { + TaskExecutorPartitionHandler retVal = new TaskExecutorPartitionHandler(); + retVal.setTaskExecutor(taskExecutor()); + retVal.setStep(step1()); + retVal.setGridSize(10); + return retVal; +} +---- + The `gridSize` attribute determines the number of separate step executions to create, so it can be matched to the size of the thread pool in the `TaskExecutor`. Alternatively, it can be set to be larger than the number of threads available, which makes the blocks of