Overview

늘 상황에 맞게 써야됨이 베이스로 깔리지만, 일을 할때 스케줄과 배치에 혼용이 비일비재하다. 정의하고 사용하기 나름이지만, 어느 솔루션에선 작업을 스케줄등록한다. 스케줄을 등록한다라고 쓴다. Context에 맞게 이해하면 되지만, 기준을 잡고 사용을 함이 중요하다고 생각한다.

Schedule이란?

스케줄링은 일정한 시간 간격으로 반복적인 작업을 수행함을 의미한다. 이를 통해 작업을 자동으로 수행하거나 주기적 혹은 특정 시간이 지난 후에 작업을 수행할 수 있고 주기적인 데이터 동기화, 정기적인 리포트 생성 등 자동화가 필요한 작업에 주로 사용한다.

Batch란?

배치는 대량의 데이터를 일괄 처리하는 작업을 수행함을 의미한다. 배치 작업은 실시간 처리가 필요 없고, 사용자 개입 없이 자동으로 실행되어야 하는 작업에 적합하다.


사용방법



* SpringSchedular


1. @EnableScheduling 설정

@SpringBootApplication
@EnableScheduling
public class TestAnythingApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestAnythingApplication.class, args);
    }
}


2. 아래 @Scheduled는 cron식 방법을 활용해서 스케줄링에 관련된 작업을 정기적으로 수행할 수 있다.

@Configuration
@Slf4j
public class BatchScheduler {
    @Scheduled(cron = "0 0/1 * * * ?")
    public void TestCronScheduler(){
        log.error("{}", this.getClass() + ".TestCronScheduler() call");
    }

}

cron이라 하여 Linux 서버내에서 잡스케줄링을 지원하는 유틸리티인데 동일한 표현식을 지원한다.


// 매일 7시
@Scheduled(cron = "0 0 7 * * *")

// 매일 6, 12시
@Scheduled(cron = "0 0 6,18 * * *")

// 매일 6시간 간격으로
@Scheduled(cron = "0 0 0/6 * * ? *")

// 매일 1분 간격으로
@Scheduled(cron = "0 0/1 * * * ")

// 매일 5분 간격으로
@Scheduled(cron = "0 0/5 * * * ")

cron 예시 및 확인사이트


* SpringBatch


SpringBatch를 생성하여 Batch처리를 할 수 있다. 본연의 역할이 있는 컴퍼넌트가 존재한다(Reader, Processor, Writer) 기본적인 구조로 Job내에 Step별로 컴퍼넌트를 통해 데이터 Read, Processing, Writer를 쉽게 할 수 있도록 설계되어 있다. 단순 작업같은 경우 Tasklet을 통해 수행 할 수도 있다. (이 내용관련 추후 다시 자세히 정리예정)

1. Build.gradle 파일내 Dependency 설정

implementation 'org.springframework.boot:spring-boot-starter-batch'

SpringBoot를 통해 사용할 예정이라 Starter를 통해 관련 Dependency를 관리한다.


2. @EnableBatchProcessing 설정

@SpringBootApplication
@EnableBatchProcessing
public class SpringBatchTestApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(SpringBatchTestApplication.class);
        application.addListeners(new ApplicationPidFileWriter());
        application.run(args);
    }
}


3. Job설정(reader, processor, writer)

@Slf4j
@Configuration
@RequiredArgsConstructor
public class TrMigrationConfig {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    private final OrdersRepository ordersRepository;
    private final AccountsRepository accountsRepository;
//--spring.batch.job.names=trMigrationJob
    @Bean
    public Job trMigrationJob(){
        return jobBuilderFactory.get("trMigrationJob")
                .incrementer(new RunIdIncrementer())
                .start(trMigrationStep())
                .build();
    }

    @JobScope
    @Bean
    public Step trMigrationStep(){
        return stepBuilderFactory.get("trMigrationStep")
                .<Orders, Accounts>chunk(5) // <src,trg> commit 단위 5
                .reader(trOrdersReader())
                .processor(trOrderProcessor())
                .writer(trOrderWriter())
////                .writer((items)->{
////                    ims.forEach(System.out::println);
//                })
                .listener(new ChunkLoggerListener())
                .build();
    }

    @StepScope
    @Bean
    public RepositoryItemReader<Orders> trOrdersReader(){
        return new RepositoryItemReaderBuilder<Orders>()
                .name("trOrdersReader")
                .repository(ordersRepository)
                .methodName("findAll")
                .pageSize(5)//chunk size가 동일하게~
                .arguments(Arrays.asList())
                .sorts(Collections.singletonMap("id", Sort.Direction.ASC))
                .build();
    }

    @StepScope
    @Bean
    public ItemProcessor<Orders, Accounts> trOrderProcessor(){
//        return new ItemProcessor<Orders, Accounts>() {
//            @Override
//            public Accounts process(Orders item) throws Exception {
//                return null;
//            }
//        }
        return (item) -> new Accounts(item);
    }

    @StepScope
    @Bean
    public ItemWriter<Accounts> trOrderWriter(){
        // #1 RepositoryItemWriterBuilder를 사용하여 적재가능.
//        return new RepositoryItemWriterBuilder<Accounts>()
//                .repository(accountsRepository)
//                .methodName("save")
//                .build();
        // #2 ItemWriter<Accounts>를 사용하여 직접 지정하여 적재가능.
        return (items)->{
            items.forEach( item -> accountsRepository.save(item) );
        };
    }
}

Step은 N가 될 수 있고, 또한 선후행처리, Job별, Step별 전후 처리등등 여러가지 기능을 제공한다.




마치며,,

Schedule, Batch 모두 상황에 맞게 쓰야되며, WebApplication과의 Coupling을 줄이고 시스템간 결합도를 느슨하게 유지, 운영 하는것이 중요하기에 성격에 맞는 Schedule, Batch를 관리해야된다. SpringSchedular는 상당히 간단하게 적용할 수 있어 상대적으로 큰개념의 SpringBatch에 대해서는 간략히 예시를 남겼다. 공부해야될것이 많군요..