Execute batch processing with Task
Tanzu Application Service can run not only resident applications (Long Running Processes) such as web applications, but also short-lived tasks. Execute batch processing using the Task function. Deploy simple batch processing using Spring Batch.
Execute a task
Let's run a simple command with the cf run-task
command.
cf run-task hello-cf -c "ls -la"
Create a Spring Batch application
cd ..
curl https://start.spring.io/starter.tgz \
-d artifactId=billing-job \
-d baseDir=billing-job \
-d type=maven-project \
-d packageName=com.example \
-d dependencies=batch,postgresql,configuration-processor \
-d applicationName=BillingJobApplication | tar -xzvf -
cd billing-job
package com.example;
public record Usage(Long id, String firstName, String lastName, Long dataUsage, Long minutes) {
}
package com.example;
import java.math.BigDecimal;
import java.math.RoundingMode;
public record Bill(Long id, String firstName, String lastName, Long dataUsage, Long minutes, BigDecimal billAmount) {
public static BigDecimal calcBillAmount(Long dataUsage, Long minutes) {
// dataUsage * 0.001 + usageMinutes * 0.01
return BigDecimal.valueOf(dataUsage).multiply(new BigDecimal("0.001"))
.add(BigDecimal.valueOf(minutes).multiply(new BigDecimal("0.01")))
.setScale(2, RoundingMode.FLOOR);
}
public static Bill fromUsage(Usage usage) {
BigDecimal billAmount = calcBillAmount(usage.dataUsage(), usage.minutes());
return new Bill(usage.id(), usage.firstName(), usage.lastName(), usage.dataUsage(), usage.minutes(), billAmount);
}
}
package com.example;
import javax.sql.DataSource;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.builder.JdbcBatchItemWriterBuilder;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class JobConfig {
@Bean
@StepScope
public FlatFileItemReader<Usage> usageItemReader(@Value("#{jobParameters['usageInfoFile']}") Resource usageInfoFile) {
return new FlatFileItemReaderBuilder<Usage>()
.name("UsageItemReader")
.resource(usageInfoFile)
.delimited()
.names("id", "firstName", "lastName", "minutes", "dataUsage")
.fieldSetMapper(fs -> new Usage(fs.readLong("id"),
fs.readString("firstName"),
fs.readString("lastName"),
fs.readLong("minutes"),
fs.readLong("dataUsage")))
.linesToSkip(1)
.build();
}
@Bean
public ItemProcessor<Usage, Bill> usageToBillItemProcessor() {
return new ItemProcessor<Usage, Bill>() {
@Override
public Bill process(Usage usage) throws Exception {
return Bill.fromUsage(usage);
}
};
}
@Bean
public ItemWriter<Bill> billItemWriter(DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Bill>()
.beanMapped()
.dataSource(dataSource)
.sql("INSERT INTO BILL_STATEMENTS (id, first_name, last_name, minutes, data_usage,bill_amount) VALUES (:id, :firstName, :lastName, :minutes, :dataUsage, :billAmount)")
.build();
}
@Bean
public Step billingStep(JobRepository jobRepository, PlatformTransactionManager transactionManager, ItemReader<Usage> itemReader, ItemProcessor<Usage, Bill> itemProcessor, ItemWriter<Bill> itemWriter) {
return new StepBuilder("BillingStep", jobRepository)
.<Usage, Bill>chunk(1000, transactionManager)
.reader(itemReader)
.processor(itemProcessor)
.writer(itemWriter)
.build();
}
@Bean
public Job billingJob(JobRepository jobRepository, Step billingStep) {
return new JobBuilder("BillingJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(billingStep)
.build();
}
}
./mvnw clean package -Dmaven.test.skip=true
cf create-service postgres on-demand-postgres-small billing-db
applications:
- name: billing-job
no-route: true
instances: 0
path: target/billing-job-0.0.1-SNAPSHOT.jar
buildpacks:
- java_buildpack_offline
services:
- billing-db
env:
JBP_CONFIG_OPEN_JDK_JRE: '{jre: {version: 17.+}}'
cf push
cf logs billing-job
cf run-task billing-job -m 128m -c ".java-buildpack/open_jdk_jre/bin/java org.springframework.boot.loader.launch.JarLauncher usageInfoFile=classpath:usageinfo.csv"
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT . ____ _ __ _ _
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT \\/ ___)| |_)| | | | | || (_| | ) ) ) )
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT ' |____| .__|_| |_|_| |_\__, | / / / /
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT =========|_|==============|___/=/_/_/_/
2024-04-10T18:10:46.04+0900 [APP/TASK/530028ad/0] OUT :: Spring Boot :: (v3.2.4)
2024-04-10T18:10:46.18+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:46.185Z INFO 7 --- [demo] [ main] com.example.BillingJobApplication : Starting BillingJobApplication v0.0.1-SNAPSHOT using Java 17.0.10 with PID 7 (/home/vcap/app/BOOT-INF/classes started by vcap in /home/vcap/app)
2024-04-10T18:10:46.19+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:46.190Z INFO 7 --- [demo] [ main] com.example.BillingJobApplication : The following 1 profile is active: "cloud"
....
2024-04-10T18:10:49.50+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.500Z INFO 7 --- [demo] [ main] com.example.BillingJobApplication : Started BillingJobApplication in 4.261 seconds (process running for 4.93)
2024-04-10T18:10:49.67+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.670Z INFO 7 --- [demo] [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=BillingJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}','usageInfoFile':'{value=classpath:usageinfo.csv, type=class java.lang.String, identifying=true}'}]
2024-04-10T18:10:49.73+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.737Z INFO 7 --- [demo] [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [BillingStep]
2024-04-10T18:10:49.87+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.871Z DEBUG 7 --- [demo] [ main] o.s.batch.core.step.tasklet.TaskletStep : Applying contribution: [StepContribution: read=5, written=5, filtered=0, readSkips=0, writeSkips=0, processSkips=0, exitStatus=EXECUTING]
2024-04-10T18:10:49.87+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.876Z DEBUG 7 --- [demo] [ main] o.s.batch.core.step.tasklet.TaskletStep : Saving step execution before commit: StepExecution: id=1, version=1, name=BillingStep, status=STARTED, exitStatus=EXECUTING, readCount=5, filterCount=0, writeCount=5 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
2024-04-10T18:10:49.89+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.890Z INFO 7 --- [demo] [ main] o.s.batch.core.step.AbstractStep : Step: [BillingStep] executed in 151ms
2024-04-10T18:10:49.91+0900 [APP/TASK/530028ad/0] OUT 2024-04-10T09:10:49.916Z INFO 7 --- [demo] [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=BillingJob]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}','usageInfoFile':'{value=classpath:usageinfo.csv, type=class java.lang.String, identifying=true}'}] and the following status: [COMPLETED] in 213ms
You can see from the log that 5 items of data were processed.
Note: If
Exit status 137 (out of memory)
occurs, please change-m 128m
to-m 256m
and re-run.
cf run-task billing-job -m 128m -c ".java-buildpack/open_jdk_jre/bin/java org.springframework.boot.loader.launch.JarLauncher usageInfoFile=https://github.com/making/fakedata/raw/master/usageinfo/usageinfo-10000-en.csv"
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT . ____ _ __ _ _
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT \\/ ___)| |_)| | | | | || (_| | ) ) ) )
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT ' |____| .__|_| |_|_| |_\__, | / / / /
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT =========|_|==============|___/=/_/_/_/
2024-04-10T19:32:29.58+0900 [APP/TASK/9d230a5e/0] OUT :: Spring Boot :: (v3.2.4)
2024-04-10T19:32:29.67+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:29.676Z INFO 6 --- [demo] [ main] com.example.BillingJobApplication : Starting BillingJobApplication v0.0.1-SNAPSHOT using Java 17.0.10 with PID 6 (/home/vcap/app/BOOT-INF/classes started by vcap in /home/vcap/app)
2024-04-10T19:32:29.68+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:29.680Z INFO 6 --- [demo] [ main] com.example.BillingJobApplication : The following 1 profile is active: "cloud"
...
2024-04-10T19:32:32.17+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:32.171Z INFO 6 --- [demo] [ main] com.example.BillingJobApplication : Started BillingJobApplication in 3.171 seconds (process running for 3.754)
2024-04-10T19:32:32.33+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:32.337Z INFO 6 --- [demo] [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=BillingJob]] launched with the following parameters: [{'run.id':'{value=3, type=class java.lang.Long, identifying=true}','usageInfoFile':'{value=https://github.com/making/fakedata/raw/master/usageinfo/usageinfo-10000-en.csv, type=class java.lang.String, identifying=true}'}]
2024-04-10T19:32:32.42+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:32.425Z INFO 6 --- [demo] [ main] o.s.batch.core.job.SimpleStepHandler : Executing step: [BillingStep]
2024-04-10T19:32:34.12+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:34.121Z DEBUG 6 --- [demo] [ main] o.s.batch.core.step.tasklet.TaskletStep : Applying contribution: [StepContribution: read=1000, written=1000, filtered=0, readSkips=0, writeSkips=0, processSkips=0, exitStatus=EXECUTING]
2024-04-10T19:32:34.12+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:34.126Z DEBUG 6 --- [demo] [ main] o.s.batch.core.step.tasklet.TaskletStep : Saving step execution before commit: StepExecution: id=3, version=1, name=BillingStep, status=STARTED, exitStatus=EXECUTING, readCount=1000, filterCount=0, writeCount=1000 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=1, rollbackCount=0, exitDescription=
...
2024-04-10T19:32:34.96+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:34.959Z DEBUG 6 --- [demo] [ main] o.s.batch.core.step.tasklet.TaskletStep : Applying contribution: [StepContribution: read=0, written=0, filtered=0, readSkips=0, writeSkips=0, processSkips=0, exitStatus=EXECUTING]
2024-04-10T19:32:34.96+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:34.961Z DEBUG 6 --- [demo] [ main] o.s.batch.core.step.tasklet.TaskletStep : Saving step execution before commit: StepExecution: id=3, version=11, name=BillingStep, status=STARTED, exitStatus=EXECUTING, readCount=10000, filterCount=0, writeCount=10000 readSkipCount=0, writeSkipCount=0, processSkipCount=0, commitCount=11, rollbackCount=0, exitDescription=
2024-04-10T19:32:34.97+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:34.972Z INFO 6 --- [demo] [ main] o.s.batch.core.step.AbstractStep : Step: [BillingStep] executed in 2s545ms
2024-04-10T19:32:34.99+0900 [APP/TASK/9d230a5e/0] OUT 2024-04-10T10:32:34.995Z INFO 6 --- [demo] [ main] o.s.b.c.l.support.SimpleJobLauncher : Job: [SimpleJob: [name=BillingJob]] completed with the following parameters: [{'run.id':'{value=3, type=class java.lang.Long, identifying=true}','usageInfoFile':'{value=https://github.com/making/fakedata/raw/master/usageinfo/usageinfo-10000-en.csv, type=class java.lang.String, identifying=true}'}] and the following status: [COMPLETED] in 2s629ms
$ cf tasks billing-job
Getting tasks for app billing-job in org handson-22297 / space demo as tmaki...
id name state start time command
2 9d230a5e SUCCEEDED Wed, 10 Apr 2024 10:27:06 UTC .java-buildpack/open_jdk_jre/bin/java org.springframework.boot.loader.launch.JarLauncher usageInfoFile=https://github.com/making/fakedata/raw/master/usageinfo/usageinfo-10000-en.csv
1 530028ad SUCCEEDED Wed, 10 Apr 2024 09:10:39 UTC .java-buildpack/open_jdk_jre/bin/java org.springframework.boot.loader.launch.JarLauncher usageInfoFile=classpath:usageinfo.csv