스프링 배치 버전업 마이그레이션 (슈퍼점프)

Syureu
NAVER Pay Dev Blog
Published in
15 min readMay 26, 2023

안녕하세요.
NAVER FINANCIAL 네이버페이 가맹점회원개발팀에서 백엔드 개발을 하고 있는 김기담입니다.

입사 후 주니어 개발자라면 한번쯤은 해보고 싶었던 레거시 코드를 최신의 것으로 만들어보는 경험을, 저희 배치서버를 마이그 하며 해볼 수 있었고, 이에 대한 이야기를 해보려고 합니다.

마이그 대상인 배치 서버

  • Spring (3.1.1.RELEASE)
  • Spring Batch (2.1.1.RELEASE)

마이그 목표

  • Spring Boot (2.7.11)
  • spring-boot-starter-batch (2.7.11, Spring Batch 4.3.8)

꽤 많은 버전을 한번에 올리게 되어 “슈퍼점프" 라는 부제도 붙이게 되었습니다, 보통은 사용하는 모듈의 버전을 점진적으로 개선하며 사용하지만, 저희 기존 배치 서버는 나름의 사연(?)과 함께 예전 버전으로 유지되고 있었기 때문에 신규 레포지토리에 새로 모듈을 추가하면서 저희가 사용하는 다른 부트 버전들과 맞추며 여러 사항을 고려해보았습니다.

우선 버전업에 따른 마이그레이션 가이드를 얻기 위해 “spring batch migration guide”와 같이 검색해봅니다.
스프링 배치는 docs.spring.io 혹은 spring-batch github 에서 관련 문서를 제공해 줍니다.

(글을 작성하는 현 시점에선 부트3버전과 그에 따른 스프링 배치 5버전이 나왔으므로 해당 가이드가 검색됩니다.)

Spring Batch 문서엔 Spring Batch JobRepository를 위한 Meta-Data Schema에 대해 설명해둔 페이지가 있는데요, 여기서 Migration DDL Scripts에 대해 확인해 보실 수 있습니다.

Migration DDL Scripts

Spring Batch provides migration DDL scripts that you need to execute when you upgrade versions. These scripts can be found in the Core Jar file under org/springframework/batch/core/migration. Migration scripts are organized into folders corresponding to version numbers in which they were introduced:

  • 2.2: Contains scripts you need to migrate from a version before 2.2 to version 2.2
  • 4.1: Contains scripts you need to migrate from a version before 4.1 to version 4.1

2.2 버전 보다 낮은 버전에서 2.2로 갈때, 4.1 버전 보다 낮은 버전에서 4.1로 갈때 필요한 DDL을 스프링 배치에서 제공한다고 되어 있습니다.
(실제로 4.3.x 라이브러리를 확인해보면 4.3 버전으로 넘어갈 때 필요한 마이그레이션 DDL 또한 있음을 알 수 있습니다)

저희는 JobRepository를 위해 mysql을 사용하고 있으므로, 스프링 배치 2.1.1에서 4.3.8 을 목표로 한다면 위의 두 DDL을 순차적으로 수행해줘야, 스프링배치 4.3.8의 JobRepository에 맞는 테이블이 준비됩니다.

여기까지 버전에 맞는 JobRepository 마이그레이션을 알아보고 나니, 과연 우리가 기존에 쓰던 JobRepository를 그대로 마이그해야될까? 라는 고민이 생겼습니다.

  • 레거시 배치 서버는 n개의 팀의 m개의 배치가 수행되고 있는 서버입니다. 이 중 저희 팀에서 관리하는 배치 잡은 약 25% 정도로, 나머지 배치 잡을 저희가 관리할 필요는 없습니다.
  • 신규 배치 서버는 오로지 저희 팀에서 관리하는 잡만 위치하면 됩니다.
  • 레거시 배치는 2.1.1 버전이므로 해당 버전의 스프링 배치 JobRepository는 BATCH_JOB_EXECUTION_PARAMS 테이블을 사용하지 않기 때문에 같은 JobRepository에 스프링 배치 4.3.8 을 사용하게 된다면 레거시 배치 서버의 JobRepository 기록을 이어서 신규 배치 서버가 수행하는 경우 JobParamter 관련 값을 얻을 수 없을 것으로 예상할 수 있습니다.
    (그리고 일반적으로 고려해봐도 “A 버전을 위해 설계된 데이터를 B 버전에서 사용한다”는 일반화된 명제만 봐도 좋은 모습은 아니죠)
  • 저희가 관리하는 배치 잡 중에서는 이전의 JobRepository 기록에 영향을 받는 잡은 없습니다.
    (잡 수행이 JobRepository에 대해서 순수합니다)

이러한 상황을 고려하여 기존의 JobRepository를 마이그하기 보다는 신규 배치 서버를 위한 새 JobRepository를 구성하게 되었습니다.

이제 우리의 목표는 레거시 모듈 버전업에서 신규 배치 모듈을 구성하고 기존의 잡을 이관하는것으로 설정해봅시다. 일반적인 스프링 부트 배치 모듈을 만드는 것과 크게 다를 바는 없지만 몇가지 포인트를 보면서 넘어가겠습니다.

JobRepository를 위한 DDL 수행

새로운 mysql 데이터베이스를 신규 배치 서버를 위해 할당받았고 JobRepository를 위해 DDL을 실행해 줍시다.
문서의 다음 내용에서 확인할 수 있습니다.

Example DDL Scripts

The Spring Batch Core JAR file contains example scripts to create the relational tables for a number of database platforms (which are, in turn, auto-detected by the job repository factory bean or namespace equivalent). These scripts can be used as is or modified with additional indexes and constraints, as desired. The file names are in the form schema-*.sql, where * is the short name of the target database platform. The scripts are in the package org.springframework.batch.core.

Multi DataSource를 위한 설정들

저희는 엔티티를 위한 데이터베이스와 JobRepository를 위한 데이터베이스가 별도로 되어 있으므로,
신규 배치모듈에선 JobRepository를 위한 dataSource를 추가합니다.

@Bean
public DataSource jobDataSource() {
...
}

@Bean
public DataSourceTransactionManager jobTransactionManager() {
return new DataSourceTransactionManager(jobDataSource());
}
@Configuration
public class BatchJobRepositoryConfig extends DefaultBatchConfigurer {

public BatchJobRepositoryConfig(
@Qualifier("jobDataSource") DataSource dataSource,
@Qualifier("jobTransactionManager") PlatformTransactionManager transactionManager) {
this.dataSource = dataSource;
this.transactionManager = transactionManager;
}
}

배치만의 로직에 의해 트랜잭션이 관리 될 수도 있지만 어댑터를 이용하거나, 서비스 빈을 주입받아 기존의 서비스 로직을 이용할 수 있습니다. 이 경우 엔티티 데이터베이스의 트랜잭션은 선언적 트랜잭션이 되어있으므로 해당 dataSource의 트랜잭션을 이용할 수 있도록 TransactionManagementConfigurer 에 엔티티 dataSource를 설정해 줍니다.

public class BatchJobDataSourceConfig implements TransactionManagementConfigurer {

private final DataSource dataSource;

@Override
@Bean
public TransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}

}

배치 모듈에서는 다른 모듈과 다르게 엔티티 dataSource의 설정값(ex : connectionProperties의 CONNECTION_TIMEOUT, ReadTimeout 등)을 다르게 사용하므로 이를 override 해야 합니다.

신규 모듈 레포지토리는 멀티 모듈 형태로 구성되어 있으므로 아래와 같은 우선순위로 properties를 불러 올 수 있습니다.

Config data files are considered in the following order:

  1. Application properties packaged inside your jar (application.properties and YAML variants).
  2. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  3. Application properties outside of your packaged jar (application.properties and YAML variants).
  4. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).

엔티티 dataSource는 배치 뿐만 아니라 다른 모든 모듈에서 사용되고 있으므로 관련 properties가 4에 의해 로딩됩니다. inside인 배치 모듈의 어느 properties에 기록하더라도 위 우선순위로는 오버라이드 할 수 없습니다.

그러므로 스프링의 ApplicationEnvironmentPreparedEvent를 이용해 필요한 프로퍼티가 모두 로딩된 이후 저희가 오버라이드 하고 싶은 프로퍼티를 적절한 우선순위를 갖도록 하여 엔티티 dataSource가 배치 모듈에서만 다른 connectionProperties를 갖도록 설정했습니다.

public class BatchDbPropertiesOverrideConfig implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

@Override public void onApplicationEvent(ApplicationEnvironmentPreparedEvent applicationEnvironmentPreparedEvent) {
...
MutablePropertySources mpss = env.getPropertySources();
...
PropertySource<?> coreApplicationActiveProfileYml = atomic.get();
...
PropertySource<?> batchDbOverridePropertySources = null;
try {
batchDbOverridePropertySources = new YamlPropertySourceLoader().load("batch-db-override-properties", new ClassPathResource("batch-db-override.yaml")).get(0);
}
...
mpss.addBefore(coreApplicationActiveProfileYml.getName(), batchDbOverridePropertySources);
...
}
public class BatchApplication {

public static void main(String[] args) {
SpringApplication app = new SpringApplication(BatchApplication.class);
app.addListeners(new BatchDbPropertiesOverrideConfig());
app.run(args);
}

}

XML 로 작성된 레거시 배치의 job config를 바로 가져와서 쓸 수 있게 만들기

레거시 배치 모듈에서 job config는 XML로 작성되어 있습니다. 신규 배치 모듈에서 추가되거나 리팩토링된 job config는 다음과 같은 예제처럼 자바 코드로 작성하기로 되어있지만 기존의 job들을 우선 가져와서 동작시켜야 한다면 다음과 같은 간단한 어노테이션을 추가해 기존 xml based job config를 바로 사용할 수 있습니다.

@Configuration
@ImportResource(value = {"classpath*:jobs/*-job.xml"})

SpringBoot 2.6.0 버전에서 deprecated된 BatchDataSourceInitializer 를 BatchDataSourceScriptDatabaseInitializer로 바꾸기

글에선 스프링 부트 버전 2.7.11 로 바로 작성하는것으로 되어있으나 실제로는 2.5.4 버전으로 작성 이후 2.7.11 로 한번 더 버전업을 진행하였습니다. 2.6.0 버전에서 BatchDataSourceInitializer는 deprecated되었으므로 api 가이드에서 제시하는 BatchDataSourceScriptDatabaseInitializer 로 변경했습니다.

레거시 배치 모듈을 버전업 하는 목표에서 여러 사항을 고려하다보니,
신규 배치 모듈 작성 및 기존 잡 이관을 하는 것으로 목표를 바꾸게 되었고
이를 위해 신규 배치 모듈을 작성하며 경험한 몇가지 포인트를 글로 회고해 보았습니다.

--

--