From 6239f9514112772f31425dc35f84ebde0c812252 Mon Sep 17 00:00:00 2001 From: Jay Bryant Date: Fri, 6 Oct 2017 11:48:23 -0500 Subject: [PATCH] Editing pass for scalability.adoc I improved readability and consistency and corrected sentence errors. No questions this time. Updating code review changes as a part of merge --- spring-batch-docs/asciidoc/scalability.adoc | 218 ++++++++++---------- 1 file changed, 110 insertions(+), 108 deletions(-) diff --git a/spring-batch-docs/asciidoc/scalability.adoc b/spring-batch-docs/asciidoc/scalability.adoc index 8f987c6b6..8e33b6668 100644 --- a/spring-batch-docs/asciidoc/scalability.adoc +++ b/spring-batch-docs/asciidoc/scalability.adoc @@ -1,4 +1,4 @@ -:batch-asciidoc: http://docs.spring.io/spring-batch/reference/html/ +:batch-asciidoc: ./ :toc: left :toclevels: 4 @@ -10,14 +10,18 @@ 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 the simplest implementation - meets your needs first: you can read and write a file of several hundred + meets your needs first. You can read and write a file of several hundred megabytes in well under a minute, even with standard hardware. When you are ready to start implementing a job with some parallel processing, Spring Batch offers a range of options, which are described in - this chapter, although some features are covered elsewhere. At a high level - there are two modes of parallel processing: single process, multi-threaded; - and multi-process. These break down into categories as well, as + this chapter, although some features are covered elsewhere. At a high level, + there are two modes of parallel processing: + +* Single process, multi-threaded +* Multi-process + +These break down into categories as well, as follows: @@ -32,7 +36,7 @@ When you are ready to start implementing a job with some parallel * Partitioning a Step (single or multi process) -Next we review the single-process options first, and then the +First, we review the single-process options. Then we review the multi-process options. [[multithreadedStep]] @@ -41,8 +45,8 @@ Next we review the single-process options first, and then the === Multi-threaded Step The simplest way to start parallel processing is to add a - `TaskExecutor` to your Step configuration, e.g. as an - attribute of the `tasklet`: + `TaskExecutor` to your Step configuration. For example, you might add an + attribute of the `tasklet`, as shown in the following example: [source, xml] @@ -52,24 +56,24 @@ The simplest way to start parallel processing is to add a ---- -In this example the taskExecutor is a reference to another bean - definition, implementing the `TaskExecutor` - interface. `TaskExecutor` is a standard Spring +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`] is a standard Spring interface, so consult the Spring User Guide for details of available implementations. The simplest multi-threaded `TaskExecutor` is a `SimpleAsyncTaskExecutor`. -The result of the above configuration will be that the Step - executes by reading, processing and writing each chunk of items +The result of the above configuration is that the `Step` + executes by reading, processing, and writing each chunk of items (each commit interval) in a separate thread of execution. Note that this means there is no fixed order for the items to be processed, and a chunk might contain items that are non-consecutive compared to the single-threaded case. In addition - to any limits placed by the task executor (e.g. if it is backed by + 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 utilised, e.g. + to ensure that a thread pool is fully utilized, as shown in the following example: [source, xml] @@ -86,28 +90,28 @@ Note also that there may be limits placed on concurrency by those resources at least as large as the desired number of concurrent threads in the step. -There are some practical limitations of using multi-threaded Steps - for some common Batch use cases. Many participants in a Step (e.g. readers - and writers) are stateful, and if the state is not segregated by thread, - then those components are not usable in a multi-threaded Step. In - particular most of the off-the-shelf readers and writers from Spring Batch +There are some practical limitations of using multi-threaded `Step` implementations + for some common batch use cases. Many participants in a `Step` (such as readers + and writers) are stateful. If the state is not segregated by thread, + then those components are not usable in a multi-threaded `Step`. In + particular, most of the off-the-shelf readers and writers from Spring Batch are not designed for multi-threaded use. It is, however, possible to work with stateless or thread safe readers and writers, and there is a sample - (parallelJob) in the Spring Batch Samples that show the use of a process + (called `parallelJob`) in the https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples[Spring Batch Samples] that show the use of a process indicator (see <>) to keep track of items that have been processed in a database input table. Spring Batch provides some implementations of - ItemWriter and - `ItemReader`. Usually they say in the - Javadocs if they are thread safe or not, or what you have to do to + `ItemWriter` and + `ItemReader`. Usually, they say in the + Javadoc if they are thread safe or not or what you have to do to avoid problems in a concurrent environment. If there is no - information in Javadocs, you can check the implementation to see + information in the Javadoc, you can check the implementation to see if there is any state. If a reader is not thread safe, it may still be efficient to use it in your own synchronizing delegator. You can synchronize the call to `read()` and as long as the processing and writing is the most expensive part of - the chunk your step may still complete much faster than in a + the chunk, your step may still complete much faster than it would in a single threaded configuration. @@ -117,11 +121,11 @@ Spring Batch provides some implementations of === Parallel Steps As long as the application logic that needs to be parallelized can - be split into distinct responsibilities, and assigned to individual steps + 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`, you could configure a flow like this: + `step3`, as shown in the following example: [source, xml] @@ -146,7 +150,7 @@ 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 parallel. Note - that the job will ensure that every flow in the split completes before + that the job ensures that every flow in the split completes before aggregating the exit statuses and transitioning. See the section on <> for more @@ -157,38 +161,37 @@ See the section on <> for more === Remote Chunking -In Remote Chunking the Step processing is split across multiple - processes, communicating with each other through some middleware. Here is - a picture of the pattern in action: +In remote chunking, the `Step` processing is split across multiple + processes, communicating with each other through some middleware. The following + image shows the pattern: .Remote Chunking image::{batch-asciidoc}images/remote-chunking.png[Remote Chunking, scaledwidth="60%"] -The Master component is a single process, and the Slaves are - multiple remote processes. Clearly this pattern works best if the Master +The master component is a single process, and the slaves are + multiple remote processes. This pattern works best if the master is not a bottleneck, so the processing must be more expensive than the - reading of items (this is often the case in practice). + reading of items (as is often the case in practice). -The Master is just an implementation of a Spring Batch - Step, with the `ItemWriter` replaced with a generic +The master is an implementation of a Spring Batch + `Step` with the `ItemWriter` replaced by a generic version that knows how to send chunks of items to the middleware as - messages. The Slaves are standard listeners for whatever middleware is - being used (e.g. with JMS they would be - `MesssageListeners`), and their role is to process + messages. The slaves are standard listeners for whatever middleware is + being used (for example, with JMS, they would be + `MesssageListener` implementations), and their role is to process the chunks of items using a standard `ItemWriter` or `ItemProcessor` plus `ItemWriter`, through the `ChunkProcessor` interface. One of the advantages of - using this pattern is that the reader, processor and writer components are + using this pattern is that the reader, processor, and writer components are off-the-shelf (the same as would be used for a local execution of the step). The items are divided up dynamically and work is shared through the - middleware, so if the listeners are all eager consumers, then load + middleware, so that, if the listeners are all eager consumers, then load balancing is automatic. -The middleware has to be durable, with guaranteed delivery and +The middleware has to be durable, with guaranteed delivery and a single consumer for each message. JMS is the obvious candidate, but other - options exist in the grid computing and shared memory product space (e.g. - Java Spaces). + options (such as Java Spaces exist in the grid computing and shared memory product space. [[partitioning]] @@ -196,37 +199,37 @@ The middleware has to be durable, with guaranteed delivery and === Partitioning Spring Batch also provides an SPI for partitioning a `Step` execution - and executing it remotely. In this case the remote participants are simply + and executing it remotely. In this case, the remote participants are `Step` instances that could just as easily have been configured and used for - local processing. Here is a picture of the pattern in action: + local processing. The following image shows the pattern: .Partitioning image::{batch-asciidoc}images/partitioning-overview.png[Partitioning Overview, scaledwidth="60%"] -The `Job` is executing on the left hand side as a sequence of `Steps`, - and one of the `Steps` is labelled as a Master. The Slaves in this picture +The `Job` runs on the left-hand side as a sequence of `Step` instances, + and one of the `Step` instances is labeled as a master. The slaves in this picture are all identical instances of a `Step`, which could in fact take the place - of the Master resulting in the same outcome for the `Job`. The Slaves are - typically going to be remote services, but could also be local threads of - execution. The messages sent by the Master to the Slaves in this pattern - do not need to be durable, or have guaranteed delivery: Spring Batch - meta-data in the JobRepository will ensure that - each Slave is executed once and only once for each `Job` execution. + of the master, resulting in the same outcome for the `Job`. The slaves are + typically going to be remote services but could also be local threads of + execution. The messages sent by the master to the slaves in this pattern + do not need to be durable or have guaranteed delivery. Spring Batch + metadata in the JobRepository ensures that + each slave is executed once and only once for each `Job` execution. The SPI in Spring Batch consists of a special implementation of `Step` - (the `PartitionStep`), and two strategy interfaces + (called the `PartitionStep`) and two strategy interfaces that need to be implemented for the specific environment. The strategy interfaces are `PartitionHandler` and - `StepExecutionSplitter`, and their role is show in - the sequence diagram below: + `StepExecutionSplitter`, and their role is shown in + the following sequence diagram: .Partitioning SPI image::{batch-asciidoc}images/partitioning-spi.png[Partitioning SPI, scaledwidth="60%"] -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 `PartitionStep` - configuration looks like this: +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: [source, xml] @@ -238,19 +241,19 @@ The `Step` on the right in this case is the "remote" Slave, so ---- -Similar to the multi-threaded step's throttle-limit - attribute, the grid-size attribute prevents the task executor from +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. -There is a simple example which can be copied and extended in the - unit test suite for Spring Batch Samples (see - `*PartitionJob.xml` configuration). +There is a simple example that can be copied and extended in the + unit test suite for https://github.com/spring-projects/spring-batch/tree/master/spring-batch-samples/src/main/resources/jobs[Spring Batch Samples] (see + `Partition*Job.xml` configuration). Spring Batch creates step executions for the partitions called - "step1:partition0", etc., so many people prefer to call the master step - "step1:master" for consistency. With Spring 3.0 you can do this using an - alias for the step (specifying the `name` attribute - instead of the `id`). + "step1:partition0", and so on. Many people prefer to call the master step + "step1:master" for consistency. You can use an + alias for the step (by specifying the `name` attribute + instead of the `id` attribute). [[partitionHandler]] @@ -260,30 +263,30 @@ Spring Batch creates step executions for the partitions called The `PartitionHandler` is the component that knows about the fabric of the remoting or grid environment. It is able to send `StepExecution` requests to the remote - `Steps`, wrapped in some fabric-specific format, like a DTO. It does not - have to know how to split up the input data, or how to aggregate the - result of multiple `Step` executions. Generally speaking it probably also - doesn't need to know about resilience or failover, since those are - features of the fabric in many cases, and anyway Spring Batch always - provides restartability independent of the fabric: a failed `Job` can - always be restarted and only the failed `Steps` will be + `Step` instances, wrapped in some fabric-specific format, like a DTO. It does not + have to know how to split the input data or how to aggregate the + result of multiple `Step` executions. Generally speaking, it probably also + does not need to know about resilience or failover, since those are + features of the fabric in many cases. In any case, Spring Batch always + provides restartability independent of the fabric. A failed `Job` can + always be restarted and only the failed `Steps` are re-executed. The `PartitionHandler` interface can have - specialized implementations for a variety of fabric types: e.g. simple + specialized implementations for a variety of fabric types, including simple RMI remoting, EJB remoting, custom web service, JMS, Java Spaces, shared - memory grids (like Terracotta or Coherence), grid execution fabrics + memory grids (like Terracotta or Coherence), and grid execution fabrics (like GridGain). Spring Batch does not contain implementations for any proprietary grid or remoting fabrics. -Spring Batch does however provide a useful implementation of - `PartitionHandler` that executes `Steps` locally in +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 namespace as above. It can - also be configured explicitly like this: + 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] @@ -299,17 +302,17 @@ Spring Batch does however provide a useful implementation of ---- -The `gridSize` determines the number of separate +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`, or else it can - be set to be larger than the number of threads available, in which case - the blocks of work are smaller. + thread pool in the `TaskExecutor`. Alternatively, it can + be set to be larger than the number of threads available, which makes + the blocks of work smaller. -The `TaskExecutorPartitionHandler` is quite - useful for IO intensive `Steps`, like copying large numbers of files or +The `TaskExecutorPartitionHandler` is + useful for IO-intensive `Step` instances, such as copying large numbers of files or replicating filesystems into content management systems. It can also be used for remote execution by providing a `Step` implementation that is a - proxy for a remote invocation (e.g. using Spring Remoting). + proxy for a remote invocation (such as using Spring Remoting). [[stepExecutionSplitter]] @@ -318,7 +321,7 @@ The `TaskExecutorPartitionHandler` is quite The `Partitioner` has a simpler responsibility: to generate execution contexts as input parameters for new step executions only (no - need to worry about restarts). It has a single method: + need to worry about restarts). It has a single method, as shown in the following interface definition: [source, java] @@ -329,12 +332,12 @@ public interface Partitioner { ---- The return value from this method associates a unique name for - each step execution (the `String`), with input + each step execution (the `String`) with input parameters in the form of an `ExecutionContext`. - The names show up later in the Batch meta data as the step name in the + The names show up later in the Batch metadata as the step name in the partitioned `StepExecutions`. The `ExecutionContext` is just a bag of name-value - pairs, so it might contain a range of primary keys, or line numbers, or + pairs, so it might contain a range of primary keys, line numbers, or the location of an input file. The remote `Step` then normally binds to the context input using `#{...}` placeholders (late binding in step scope), as illustrated in the next @@ -343,21 +346,21 @@ The return value from this method associates a unique name for The names of the step executions (the keys in the `Map` returned by `Partitioner`) need to be unique amongst the step - executions of a `Job`, but do not have any other specific requirements. - The easiest way to do this, and to make the names meaningful for users, + executions of a `Job` but do not have any other specific requirements. + The easiest way to do this (and to make the names meaningful for users) is to use a prefix+suffix naming convention, where the prefix is the name of the step that is being executed (which itself is unique in the `Job`), and the suffix is just a counter. There is a `SimplePartitioner` in the framework that uses this convention. -An optional interface +An optional interface called `PartitionNameProvider` can be used to provide the partition names separately from the partitions - themselves. If a Partitioner implements - this interface then on a restart only the names will be queried. - If partitioning is expensive this can be a useful optimisation. - Obviously the names provided by the + themselves. If a `Partitioner` implements + this interface, then, on a restart, only the names are queried. + If partitioning is expensive, this can be a useful optimization. + The names provided by the `PartitionNameProvider` must match those provided by the `Partitioner`. @@ -367,15 +370,15 @@ An optional interface ==== Binding Input Data to Steps It is very efficient for the steps that are executed by the - `PartitionHandler` to have identical configuration, and for their input + `PartitionHandler` to have identical configuration and for their input parameters to be bound at runtime from the `ExecutionContext`. This is easy to do with the StepScope feature of Spring Batch (covered in more - detail in the section on <>). For example + detail in the section on <>). For example, if the `Partitioner` creates - `ExecutionContext` instances with an attribute key + `ExecutionContext` instances with an attribute key called `fileName`, pointing to a different file (or directory) for each step invocation, the - `Partitioner` output might look like this: + `Partitioner` output might resemble the content of the following table: .Example step execution name to execution context provided by `Partitioner` targeting directory processing @@ -388,7 +391,7 @@ It is very efficient for the steps that are executed by the Then the file name can be bound to a step using late binding to - the execution context: + the execution context, as shown in the following example: [source, xml] @@ -398,4 +401,3 @@ Then the file name can be bound to a step using late binding to ---- -