본문 바로가기
카테고리 없음

배치 만들기

by jejeongeun 2024. 8. 16.

앞에서 http 라이브러리를 이용하여 외부 데이터를 받아오는 방법에 대하여 작성하였다.

 

받아온 외부 데이터는 서비스 오픈 전 dummy 데이터를 한꺼번에 테이블에 미리 저장해주고, 이후 신규로 업데이트 되는 데이터들에 대하여 배치 파일을 이용하여 주기적으로 실행하여 업데이트 해주기로 하였다.

 

그래서 이번에는 배치에 대해 알아보고 개인 프로젝트에 적용해보기로 하였다.

1. 배치란?

배치는 단순하게 설명해서 외부 상호 작용이나 중단이 필요하지 않은 방식으로 한정된 양의 데이터를 처리하는 것이다. 회원에게 일괄적으로 메시지를 보내거나, 정산/통계 업무 등에도 사용될 수 있다.

 

사진과 같이 Batch는 별도의 서버에서 관리하는게 좋다.
별도의 서버에서 관리되면 대량의 데이터가 한 번에 처리될 때 Application 서버의 부하를 줄여주기도 하며, Application 서버의 장애 발생 시, 영향을 받지 않고 실행될 수 있기 때문이다. 

2. 배치 파일 만들기

우선, 배치 파일을 먼저 만들어보자.
이번에 작성될 배치 파일은 목적은 매일 release되는 노래 목록들을 DB에 insert 시켜주는 것이다.

public ResponseEntity execute() throws ParseException {
	//1) 외부 API에서 데이터를 받아온다.
	ResponseEntity<String> getSongApi = Util.getSongListFromApi(BrandType.kumyoung);  
  
	//2) JSON 타입으로 리턴된 데이터를 SONG 객체로 parsing한다.
	JSONParser jsonParser = new JSONParser();  
	JSONArray jsonArray = (JSONArray) jsonParser.parse(getSongApi.getBody());  
  
	List<Song> songList = new ArrayList<>();  
	for (Object object : jsonArray) {  
        JSONObject jsonObject = (JSONObject) object;  
		BrandType brand = BrandType.valueOf(jsonObject.get("brand").toString());  
		Long no = Long.parseLong(jsonObject.get("no").toString());  
		String title = jsonObject.get("title").toString();  
		String singer = jsonObject.get("singer").toString();  

		Song song = new Song(brand, no, title, singer);  
		songList.add(song);  
	}  
  
	//3) List에 담김 Song 데이터를 DB에 insert한다.
	songService.create(songList);  
	
	return ResponseEntity.ok().build();  
}
 
 

코드는 위와 같이 단순히 외부 데이터를 가져와 parsing을 거쳐 DB에 저장해준다.

 

실제 사용되는 로직의 코드만 작성하여 주면 되기 때문에 어려움은 없어 보이지만, 여러 일련의 작업들을 처리해주거나 배치가 실행된 후 각 Job/Tasklet에 대한 결과를 바로 확인하기 어렵다는 단점이 있다.

 

각 Job/Tasklet에 대한 결과와 여러 일련의 과정을 거쳐야 한다면 Spring Batch를 이용하여 작성하면 관리하기가 쉬워진다.

3. Spring Batch를 이용하여 배치 만들기

이번에는 Spring Batch를 이용하여 만들어보자.
우선 작성된 코드는 SpringBoot 3 버전을 사용하여 작성된 코드로, 이전 2 버전과는 달라진 점들이 있다.

 

변경되는 사항은 아래 영상을 확인 해보길 바란다.

 

위에서도 설명했듯이 Spring Batch를 사용하면 일련의 작업을 Job, Step, Tasklet 등의 객체를 이용하여 작성할 수 있다. 배치 실행 시, 성공 여부 등과 같은 메타 데이터를 제공해준다. 여러 Job들의 실행 순서등을 정해주는 편의 메서드 등 배치 파일 작성 시 필요한 편의 메소드가 많이 제공된다.

 

여기서 제공되는 메타데이터란 아래의 6개의 테이블에서 제공되는 정보들을 말한다.
이전에 실행한 Job의 정보, Job안의 여러 Step의 성공/실패 여부 등의 데이터를 확인할 수 있다.

 

더 자세한 내용은 아래 링크를 확인해보자.

 

Meta-Data Schema :: Spring Batch

The Spring Batch Metadata tables closely match the domain objects that represent them in Java. For example, JobInstance, JobExecution, JobParameters, and StepExecution map to BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_JOB_EXECUTION_PARAMS, and BATCH_ST

docs.spring.io

 

3.1 Tasklet을 이용한 Spring Batch

Tasklet을 이용한 Spring Batch 코드는 아래와 같다.

@Slf4j  
@Configuration  
public class JobConfig extends BatchAutoConfiguration {  
  
	private final SongService songService;  
  
	public JobConfig(SongService songService) {  
		this.songService = songService;  
	}  
  
	@Bean  
	public Job songJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) {  
		return new JobBuilder("getSongApi", jobRepository)  
            .start(getKumyoungSongApiStep(jobRepository, transactionManager))  
            .next(getTjSongApiStep(jobRepository, transactionManager))  
            .build();  
	}  
  
	@Bean  
	public Step getKumyoungSongApiStep(JobRepository jobRepository, PlatformTransactionManager platformTransactionManager){  
		return new StepBuilder("KumyoungSongApiStep", jobRepository)  
            .tasklet(new KumyoungSongApiTasklet(songService), platformTransactionManager)  
            .build();  
	}
	...
}
 
 

songJob()에서 실행될 여러개의 Step을 start().next().next()…을 이용하여 실행될 순서를 지정할 수 있다.
getKumyoungSongApiStep()에서는 실제 실행되는 Step의 이름과 실행될 로직의 메소드를 tasklet()에 지정해 주었다.

public class KumyoungSongApiTasklet implements Tasklet {  

	private final SongService songService;  
  
	@Override  
	public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws ParseException {  

	//실제 실행될 서비스 로직은 2.에서 작성한 코드와 동일하여 생략  
	return RepeatStatus.FINISHED;  
	}  
  
}
 
 

KumyoungSongApiTasklet 클래스에서는 서비스 로직을 작성한다.
2.에서 작성한 코드와 차이는 없지만 Tasklet 인터페이스 클래스의 execute() 메소드를 오버라이드하여 그 내부에 실행되는 로직 코드를 작성한 점이 다르다.

3.2 Chunk을 이용한 Spring Batch

ItemReader/ItemWriter/ItemProcessor을 이용한 Spring Batch 코드는 아래와 같다.

@Slf4j  
@Configuration  
public class JobConfig extends BatchAutoConfiguration {  
  
	private final SongService songService;
  
	@Bean  
	public Job songJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
	    return new JobBuilder("getSongApi", jobRepository)  
	        .start(getKumyoungSongChunk(jobRepository, transactionManager))  
	        .next(getTjSongChunk(jobRepository, transactionManager))  
	        .incrementer(new RunIdIncrementer())  
	    	.build();
	}  
  
	@Bean  
	public Step getKumyoungSongChunk(JobRepository jobRepository, PlatformTransactionManager transactionManager) {  
	    return new StepBuilder("getKumyoungSongChunk", jobRepository)  
	        .chunk(50, transactionManager)  
	        .reader(songListReader(BrandType.KUMYOUNG))  
	        .processor(songListProcessor())  
	        .writer(songListWriter())  
	        .build();  
	}  
  
	@Bean  
	public Step getTjSongChunk(JobRepository jobRepository, PlatformTransactionManager transactionManager) {  
	    return new StepBuilder("getTjSongChunk", jobRepository)  
	        .chunk(50, transactionManager)  
	        .reader(songListReader(BrandType.TJ))  
	        .processor(songListProcessor())  
	        .writer(songListWriter())  
	        .build();  
	}  
  
	protected ItemReader<Song> songListReader(BrandType brand) {  
	    return new ListItemReader<>(externalService.getSongListByReleaseDate(brand));  
	}  
	  
	protected ItemProcessor<? super Object, ?> songListProcessor() {  
	    return item -> item;  
	}  
	  
	protected ItemWriter<? super Object> songListWriter() {  
	    return items -> items.forEach((item) -> songService.create((Song) item));  
	}
}
 
 

Tasklet을 이용하여 작성한 스프링 배치 코드와 비교해보면 chuck size를 지정해준 것을 확인할 수 있다. chunk size에 기입한 데이터의 갯수만큼 하나의 트랜잭션으로 묶이면, 배치 실행 중 오류가 발생하면 오류가 발생한 chunk의 데이터만 롤백이 된다.

그래서 많은 양의 데이터를 한꺼번에 처리할 경우, 발생할 수 있는 예외를 생각하면 적당한 사이즈의 chunk size를 지정하는 것이 중요하다.

 

Spring Batch를 이용하여 배치 파일을 만들어두면, 여러 일련의 과정들의 순서들과 배치 실행 결과들에 대한 메타데이터를 관리하기 쉬워진다. 하지만, Spring Batch 라이브러리를 사용하지 않아도 충분히 작성할 수 있는 경우라면 불필요한 라이브러리를 추가하는 것보다 서비스 로직만 작성하는 게 좋아 보인다.

 

나는 이후에 추가될 Job을 편하게 관리하기 위해 Spring Batch를 이용하여 배치를 관리하기로 하였다.

4. Trigger

이제, 배치 파일을 만들었다면 이제는 언제 어떻게 실행시킬지를 정해야 한다.
이 때, 언제 어떻게를 정하는 방법에는 아래와 같은 방법들이 있다.

4.1 @Scheduled

스프링에서 기본적으로 제공하는 annotation이다.
하지만, 해당 방법은 배치의 사용 목적(실행 빈도)에 따라 사용 유무가 달라질 거 같다.

 

배치의 목적에 따라 다르겠지만 내가 이번에 사용할 배치의 Trigger는 1일/1주일 단위로 실행되는 배치들이다. @Scheduled annotation을 이용할 경우 웹 서버가 항상 실행되어 있어야 한다. 서버 자체가 항상 실행되어 있다는 것만으로도 리소스 낭비가 발생한다.

 

다른 방법들과 비교하여 가장 간단한 방법이긴 하지만, 배치 주기를 생각하였을 때 최적의 방법은 아닌거 같아서 다른 방법을 사용해보기로 하였다.

4.2 cronTab

cronTab은 리눅스 서버 내에서 배치의 실행 시간을 지정하는 방법이다.
별도의 서버를 띄울 필요도 없고, 배치 실행 실패 시에도 mail을 받아 볼 수 있는 편의 기능도 제공되기 때문에 cronTab을 이용하려 하였지만, 이번에 적용할 개인 프로젝트에서는 배치 파일이 2~3개 정도 사용될 예정이기 때문에 여러 개의 배치 파일을 관리하거나 실행 결과를 즉각적으로 확인할 수 있는 편의성이 떨어져 다른 방법을 사용하기로 하였다.

4.3 Jenkins

최종적으로 선택한 방법은 바로 Jenkins이다.
Jenkins를 이용해서도 배치 실행 주기를 설정하여 실행시켜줄 수 있다.
하지만 젠킨스도 별도의 서버가 필요하지만, 실행 이력, 성공 유/무 등을 간단한 UI로 제공해주기 때문에 cronTab을 이용했을 때보다 관리가 쉬워진다.

 

Jenkins를 이용한 배치 파일 설정은 아래와 같다.

 

1) Item Name 지정 후, Freestyle Project를 선택 2) Build periodically를 선택하여 실행될 주기를 지정 (여기서 나는 사용자들의 사용 빈도가 가장 낮은 시간인 매일 오전 7시에 실행시키도록 지정해 주었다.) 

 

3) 다음 배치 파일 실행하는 명령어를 입력해주었다. (테스트를 위해 로컬 윈도우에서 실행시켰기 때문에 Execute Windows batch command를 선택하여 지정해 주었지만, 리눅스 서버에서는 Execute shell에서 실행 명령어를 입력해주면 된다. 

 

 4) 그리고 빌드 후, Console Output의 결과를 확인하면 아래와 같이 정상적으로 빌드 후 실행된 것을 확인할 수 있다. 

5. 결론

이번에도 새로운 기능을 공부하고 실제 적용까지 해보았다.

 

배치 로직의 복잡도나 운영 서버 구성 등에 따라 적용할 수 있는 여러 방법들이 있지만, 차후에 배치의 Job이 추가될 예정이고 각 Job의 실행 결과들을 편하게 관리하기 위해 나의 프로젝트에서는 Spring Batch + Jenkins를 이용하여 배치를 관리하기로 하였다.


참고 사이트:

 

Getting Started | Creating a Batch Service

A common paradigm in batch processing is to ingest data, transform it, and then pipe it out somewhere else. Here, you need to write a simple transformer that converts the names to uppercase. The following listing (from src/main/java/com/example/batchproces

spring.io

 

Spring | Batch

Batch The ability of batch processing to efficiently process large amounts of data makes it ideal for many use cases. Spring Batch’s implementation of industry-standard processing patterns lets you build robust batch jobs on the JVM. Adding Spring Boot a

spring.io