[트러블슈팅] TrendPick 프로젝트 / 추천 시스템 Spring batch 도입 및 이슈

2023. 8. 29. 18:59트러블슈팅

 

추천 알고리즘은 서브 쿼리문을 사용할 뿐 아니라, 애플리케이션 단위에서도 복잡한 데이터 변환 과정을 거치게 된다.

 

실제 운영 중인 서비스라고 가정한다면, 회원의 수가 많아지면 많아질 수록 전체적인 성능이 많이 떨어질 것이고, 데이터 변환 중간에서 오류가 발생하면 데이터 이상 현상이 발생할 가능성이 매우 높다.

 

따라서 해당 시스템의 안정성과 견고성을 위해서 Spring batch를 도입하여 시스템의 안정성을 확보하고자 했다.

 

[문제발생1]

우선적으로 스프링 배치가 정상적으로 작동하기 위한 메타 테이블이 생성되지 않았다.

 

공식 문서와 블로그를 찾아 보니 스프링 배치는 최근에 5.0 버전으로 업데이트 되면서  @EnableBatchProcessing가 JobRepository, JobLauncher, StepScope, JobScope 등의 Bean을 등록 해주는 등 자동으로 기본 설정을 해주는 어노테이션을  아래와 같이 DefaultBatchConfiguration을 상속하여 사용하면 된다고 한다. 

 

하지만 변경해주었음에 불구하고 여전히 메타 테이블이 생성되지 않았다.

 

[해결]

그래서 스프링 배치의 기본 설정을 해준다는 BatchAutoConfiguration를 보니 아래와 같이 명시되어 있었다.

@EnableBatchProcessing을 사용하거나 DefaultBatchConfiguration을 상속 받게되면 해당 Bean을 무시한다고 한다.

@AutoConfiguration(after = { HibernateJpaAutoConfiguration.class, TransactionAutoConfiguration.class })
@ConditionalOnClass({ JobLauncher.class, DataSource.class, DatabasePopulator.class })
@ConditionalOnBean({ DataSource.class, PlatformTransactionManager.class })
@ConditionalOnMissingBean(value = DefaultBatchConfiguration.class, annotation = EnableBatchProcessing.class)
@EnableConfigurationProperties(BatchProperties.class)
@Import(DatabaseInitializationDependencyConfigurer.class)
public class BatchAutoConfiguration

 

즉, @EnableBatchProcessing이나  DefaultBatchConfiguration 을 상속하는 것은 스프링 배치를 사용하기 위해 꼭 필요한 설정이 아닌, 스프링 부트의 자동설정을 애플리케이션에 맞게 커스텀하는 용도로 사용된다고 한다.

 

그래서 둘 다 제거하고 실행하니 메타 테이블이 생성되는 것을 볼 수 있었다. (참 아이러니하다..)

 


그렇다면 커스텀이 필요하다면 어떻게 해야 할까..? 

 

--> JobLauncherApplicationRunner, BatchDataSourceScriptDatabaseInitializer 등의 빈 또한 등록되지 않기 때문에 수동으로 빈을 등록해줘야 한다. 

 

==예시==
@Configuration
@EnableConfigurationProperties(BatchProperties.class)
public class BatchConfig {

  // batchDatasource 사용을 위한 수동 빈 등록
  @Bean
  @ConditionalOnMissingBean
  @ConditionalOnProperty(prefix = "spring.batch.job", name = "enabled", havingValue = "true", matchIfMissing = true)
  public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer,
      JobRepository jobRepository, BatchProperties properties) {
    JobLauncherApplicationRunner runner = new JobLauncherApplicationRunner(jobLauncher, jobExplorer, jobRepository);
    String jobNames = properties.getJob().getName();
    if (StringUtils.hasText(jobNames)) {
      runner.setJobName(jobNames);
    }
    return runner;
  }

  // batchDatasource 사용을 위한 수동 빈 등록
  @Bean
  @ConditionalOnMissingBean(BatchDataSourceScriptDatabaseInitializer.class)
  BatchDataSourceScriptDatabaseInitializer batchDataSourceInitializer(DataSource dataSource,
      @BatchDataSource ObjectProvider<DataSource> batchDataSource, BatchProperties properties) {
    return new BatchDataSourceScriptDatabaseInitializer(batchDataSource.getIfAvailable(() -> dataSource),
        properties.getJdbc());
  }
  
  =====커스텀 할 부분========
  @BatchDataSource
  @ConfigurationProperties(prefix = "spring.datasource.batch.hikari")
  @Bean("batchDataSource")
  public DataSource batchDataSource() {
      return DataSourceBuilder.create().type(HikariDataSource.class).build();
  }

  @Bean
  public PlatformTransactionManager batchTransactionManager(
          @Qualifier("batchDataSource") DataSource batchDataSource) {
      return new DataSourceTransactionManager(batchDataSource);
  }
}
==커스텀 적용==
@EnableBatchProcessing(dataSourceRef = "batchDataSource", transactionManagerRef = "batchTransactionManager")
@Import({BatchConfig.class})
public class BatchApplication {

    public static void main(String[] args) {
        SpringApplication springApplication =
                new SpringApplicationBuilder(BatchApplication.class).web(NONE).build();
        springApplication.run(args);
    }
}

 


[문제발생 2]

작성해둔 배치 로직이 정상적으로 수행되는지 확인하기 위해 yml에 아래와 같이 작성 후에 job의 이름을 명시해줌으로써 해당 잡을 테스트 하려고 했다.

batch:
  job:
    names: ${job.name:NONE}

하지만 작동이 되지 않았다.

 

이 문제 또한 5.0버전으로 업데이트 되며 다중 Job 실행이 되지 않도록 변경되었다.

 

기존에는 하나의 배치에 여러개의 Job을 한꺼번에 실행할 수 있었다. 하지만 이제 부트가 실행될 때 하나의 Job 을 실행시킨다.

이에 따라서  names가 아닌 name으로 환경변수도 변경되었다.

batch:
  job:
    name: ${job.name:NONE}

 

[해결]

현재는 job을 임시주문 삭제, 추천 시스템, 정산 처리 3가지밖에 없기 때문에 하나씩 실행시키며 테스트가 가능하다.

하지만 job의 개수가 많아진다면 ?

또는 애플리케이션이 무거워져서 한 번 실행할 때 많은 시간을 소요한다면 ?

굉장히 비효율적인 방법이 된다.

 

따라서 테스트를 위한 Job 컨트롤러를 만들어서 개발환경에서만 접근이 가능하도록 설정해준 후 job의 개수대로 메소드를 만들어서 테스트를 실행했다.

====예시 ====
@Controller
@RequiredArgsConstructor
@RequestMapping("/job")
@Profile("dev")
public class JobTestController {

    private final JobConfig jobConfig;

    @GetMapping("/test")
    @ResponseBody
    public String test(){
        try {
            jobConfig.performMakeRecommendProductJob();
        } catch (Exception e) {
            throw new RuntimeException("추천 잡실행 실패");
        }
        return "추천 잡실행 성공";
    }

실제 Job의 동작은 @Scheduled로 동작하기에 특정 파라미터로 원하는 데이터 처리가 이루어지는지를 중점으로 테스트를 진행했다.

 

* 오버 엔지니어링

해당 프로젝트는 실제 서비스를 출시하기 위함이 아닌 다양한 기술을 이해하고 접목시켜봄으로써 실무를 가정하고 진행하는 트레이닝 프로젝트 개념이기 때문에 좋은 경험이라고 생각한다 !