티스토리 뷰

이 글에서는 Spring Batch 5에서 메타데이터 관리와 Reader-Writer 데이터소스 분리 방법을 설명합니다. Spring Batch 공식 문서를 기준으로 메타데이터의 역할과 데이터소스 분리 전략을 기술합니다.

소개 · 배경

Spring Batch는 배치 작업의 실행 상태를 추적하고 관리하기 위해 메타데이터를 사용합니다. Spring Batch Reference Manual에 따르면, 메타데이터는 JobRepository를 통해 관리되며, 배치 작업의 실행 정보, Step 실행 정보, ExecutionContext 등을 저장합니다.

 

메타데이터는 기본적으로 애플리케이션의 주요 데이터베이스에 저장됩니다. 그러나 Spring Batch Reference Manual에 따르면, 메타데이터와 비즈니스 데이터를 분리하는 것이 권장됩니다. 메타데이터는 배치 작업의 실행 상태를 추적하기 위해 빈번하게 읽고 쓰는 작업이 발생하며, 이러한 작업이 비즈니스 데이터의 성능에 영향을 미칠 수 있습니다.

 

메타데이터 분리 운영이 필요한 이유는 다음과 같습니다. 메타데이터와 비즈니스 데이터의 스키마를 분리함으로써 각 데이터의 무결성을 독립적으로 유지할 수 있습니다. 메타데이터의 빈번한 읽기/쓰기 작업이 비즈니스 데이터의 성능에 영향을 미치지 않도록 분리할 수 있습니다. 메타데이터와 비즈니스 데이터를 별도로 관리함으로써 데이터베이스의 유지보수와 백업 작업을 효율적으로 수행할 수 있습니다.

 

Spring Batch 메타데이터 개요

JobRepository

JobRepository는 Spring Batch API Javadoc에 정의된 인터페이스로, 배치 작업의 메타데이터를 관리하는 역할을 담당합니다. Spring Batch Reference Manual에 따르면, JobRepository는 JobExecution, StepExecution, ExecutionContext 등의 정보를 저장하고 조회합니다.

 

JobRepository는 배치 작업의 실행 상태를 추적하기 위해 설계되었습니다. 작업 실행 중 발생하는 모든 이벤트(시작, 완료, 실패 등)를 메타데이터 테이블에 기록하며, 이를 통해 작업의 실행 이력을 추적할 수 있습니다. 또한 재시작 가능한 작업을 지원하기 위해 ExecutionContext를 저장하여, 장애 발생 시 마지막 처리 지점부터 재개할 수 있도록 합니다.

StepExecution과 JobExecution

StepExecution과 JobExecution은 Spring Batch API Javadoc에 정의된 클래스로, 각각 Step과 Job의 실행 정보를 담는 객체입니다. Spring Batch Reference Manual에 따르면, JobExecution은 Job의 실행 정보를 저장하며, StepExecution은 Step의 실행 정보를 저장합니다.

 

JobExecution은 Job의 실행 상태, 시작 시간, 종료 시간, 실행 컨텍스트 등의 정보를 포함합니다. StepExecution은 Step의 실행 상태, 읽은 아이템 수, 처리한 아이템 수, 쓰인 아이템 수, 스킵된 아이템 수 등의 정보를 포함합니다. 이러한 정보는 메타데이터 테이블에 저장되며, 작업 실행 이력을 추적하는 데 사용됩니다.

ExecutionContext 저장 구조 및 의미

ExecutionContext는 Spring Batch API Javadoc에 정의된 인터페이스로, Step 실행 중 상태를 저장하는 역할을 담당합니다. Spring Batch Reference Manual에 따르면, ExecutionContext는 키-값 쌍으로 데이터를 저장하며, StepExecution과 연관되어 메타데이터 테이블에 저장됩니다.

 

ExecutionContext는 재시작 가능한 작업을 지원하기 위해 설계되었습니다. 작업 실행 중 ExecutionContext에 저장된 상태 정보는 메타데이터 테이블에 저장되며, 장애 발생 시 이 정보를 기반으로 마지막 처리 지점부터 재개할 수 있습니다. 예를 들어, ItemReader가 현재 읽기 위치를 ExecutionContext에 저장하면, 재시작 시 이 위치부터 읽기를 재개할 수 있습니다.

트랜잭션 경계 및 @EnableBatchProcessing

Spring Batch Reference Manual에 따르면, Spring Batch는 청크 단위로 트랜잭션을 관리합니다. 각 청크는 독립적인 트랜잭션으로 실행되며, 청크 처리 중 예외가 발생하면 해당 청크만 롤백됩니다. 이러한 트랜잭션 관리는 PlatformTransactionManager를 통해 수행됩니다.

 

@EnableBatchProcessing은 Spring Batch API Javadoc에 정의된 어노테이션으로, Spring Batch 인프라를 활성화하는 역할을 담당합니다. Spring Batch Reference Manual에 따르면, 이 어노테이션은 JobRepository, JobLauncher, StepBuilderFactory, JobBuilderFactory 등의 빈을 자동으로 구성합니다.

 

@EnableBatchProcessing은 dataSourceRef와 transactionManagerRef 속성을 통해 메타데이터 전용 데이터소스와 트랜잭션 매니저를 지정할 수 있습니다. dataSourceRef는 JobRepository가 사용할 데이터소스의 빈 이름을 지정하며, transactionManagerRef는 해당 데이터소스에 대한 트랜잭션 매니저의 빈 이름을 지정합니다. 이러한 설정을 통해 메타데이터와 비즈니스 데이터의 트랜잭션 경계를 분리할 수 있습니다.

Business DB와 MetaData DB 분리 전략

Spring Batch Reference Manual에 따르면, 메타데이터와 비즈니스 데이터를 분리하는 전략은 다음과 같습니다. 메타데이터는 별도의 데이터베이스에 저장하며, JobRepository는 메타데이터 전용 데이터소스를 사용합니다. 비즈니스 데이터는 별도의 데이터소스를 사용하며, ItemReader와 ItemWriter는 비즈니스 데이터소스를 사용합니다.

 

이러한 분리 전략은 트랜잭션 경계를 명확히 하기 위해 설계되었습니다. 메타데이터 트랜잭션과 비즈니스 데이터 트랜잭션을 분리함으로써, 각 트랜잭션이 독립적으로 관리될 수 있습니다. 예를 들어, 비즈니스 데이터 처리 중 예외가 발생하여 롤백되더라도, 메타데이터는 이미 커밋되어 작업 실행 이력이 유지됩니다.

 

@Slf4j
@Configuration
@EnableBatchProcessing(
    dataSourceRef = "batchMetaWriterDataSource",
    transactionManagerRef = "batchMetaWriterTransactionManager"
)
public class BatchConfig extends BatchAutoConfiguration {
}

코드 설명:

@EnableBatchProcessing 어노테이션은 Spring Batch API Javadoc에 정의된 어노테이션으로, Spring Batch 인프라를 활성화하는 역할을 담당합니다. dataSourceRef 속성은 메타데이터를 관리하는 데이터소스의 빈 이름을 지정하며, transactionManagerRef 속성은 해당 데이터소스에 대한 트랜잭션 매니저의 빈 이름을 지정합니다.

 

Spring Batch 공식 모델에서 이 설정은 JobRepository가 메타데이터 전용 데이터소스를 사용하도록 합니다. JobRepository는 JobExecution, StepExecution, ExecutionContext 등의 정보를 메타데이터 데이터소스에 저장하며, 비즈니스 데이터와 독립적으로 관리됩니다

 

Spring Boot는 Spring Batch를 자동으로 구성하기 위해 BatchAutoConfiguration 클래스를 제공합니다:

@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 {
    public BatchAutoConfiguration() {
    }

    @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 jobName = properties.getJob().getName();
        if (StringUtils.hasText(jobName)) {
            runner.setJobName(jobName);
        }

        return runner;
    }

   ...
   
}

코드 설명:

BatchAutoConfiguration은 Spring Boot의 자동 구성 메커니즘을 통해 Spring Batch 인프라를 자동으로 구성하는 클래스입니다. @AutoConfiguration 어노테이션은 이 클래스가 자동 구성 클래스임을 나타내며, after 속성을 통해 HibernateJpaAutoConfiguration과 TransactionAutoConfiguration 이후에 실행되도록 설정합니다.

 

@ConditionalOnMissingBean과 @ConditionalOnMissingBean(annotation = {EnableBatchProcessing.class}) 조건은 @EnableBatchProcessing 어노테이션이 없을 때만 자동 구성이 활성화되도록 합니다. @EnableBatchProcessing 어노테이션을 사용하면 수동으로 Spring Batch 인프라를 구성하므로, 자동 구성이 비활성화됩니다.

 

JobLauncherApplicationRunner는 애플리케이션 시작 시 배치 작업을 자동으로 실행하는 컴포넌트입니다. @ConditionalOnProperty 조건을 통해 spring.batch.job.enabled 속성이 true일 때만 생성됩니다. 이는 @EnableBatchProcessing을 사용하여 메타데이터 데이터소스를 분리하는 경우에도 동일하게 동작합니다.

 

 

 

Business Logic 에서 접근하는 데이터베이스 접근 트랜잭션을 세팅하기  

Spring Batch는 메타데이터 관리 트랜잭션비즈니스 로직 트랜잭션을 명확히 분리할 수 있도록 설계되어 있습니다.

이를 위해 @EnableBatchProcessing에서 사용하는 트랜잭션 매니저와, 실제 Step에서 사용하는 트랜잭션 매니저를 서로 다르게 설정할 수 있습니다.

 

예를 들어, Chunk 단위 데이터 처리(Read → Process → Write)는 아래와 같이 별도의 트랜잭션 매니저(secondaryWriterTransactionManager)가 관리하는 트랜잭션 범위 안에서 수행하도록 할 수 있습니다. 데이터 커밋/롤백은 비즈니스 데이터베이스 기준으로 처리됩니다. Batch 메타데이터 트랜잭션과 완전히 분리된 트랜잭션 경계를 가집니다.

 

    @Bean(name = CodeVal.## + CodeVal.###)
    @JobScope
    public Step OutboundInvoiceJobStep1(JobRepository jobRepository
            , @Qualifier("secondaryWriterTransactionManager") PlatformTransactionManager secondaryWriterTransactionManager) {
        log.info("OutboundInvoiceJobStep1 started ... ");
        log.info("## {} - {} Step 실행 ##", CodeVal.##, CodeVal.###);

        return new StepBuilder(CodeVal.## + CodeVal.###, jobRepository)
            .listener(new ReadItemCountStepListener())
            .<ReleasedProductApiResponse, CSVRow>chunk(CHUNK_SIZE_1000, secondaryWriterTransactionManager)
            .reader(OutboundInvoiceStep1Reader())
            .processor(new OutboundInvoiceStep1Processor())
            .writer(OutboundInvoiceStep1Writer())
            .transactionManager(secondaryWriterTransactionManager)
            .build();
    }

 

[코드 3] Spring Batch 5 에서 Step 의 Transaction 을 세팅하기 



데이터소스에 대한 읽기 작업과 쓰기 작업의 부하를 나누기 

대량 데이터 처리 환경에서는 **읽기(Read)**와 쓰기(Write) 작업의 부하 특성이 다릅니다. Spring Batch는 ItemReader와 ItemWriter가 서로 다른 리소스 설정을 사용할 수 있도록 설계되어 있으며, 이를 통해 데이터베이스 부하를 효과적으로 분산할 수 있습니다.

 

 

MyBatis ItemReader / ItemWriter 설정 분리

MyBatis 기반 배치 처리에서는 다음과 같은 설정 분리가 핵심입니다.

  • ItemReader : sqlSessionReaderFactory
  • ItemWriter : SqlSessionFactory

이 구성의 의미는 다음과 같습니다.

  • sqlSessionReaderFactory
    • 읽기 전용 데이터소스(Read Replica 등)에 연결
    • 대량 조회 트래픽을 쓰기 DB와 분리
  • SqlSessionFactory
    • 읽기-쓰기 범용 데이터소스(Primary DB)에 연결
    • INSERT / UPDATE  트랜잭션 전담

Spring Batch 관점에서 ItemReader와 ItemWriter는 서로 다른 트랜잭션 참여자(Resource)로 취급될 수 있으며,

각각 독립적인 데이터소스 설정을 가질 수 있습니다.

 

이 구조는 다음을 가능하게 합니다.

  • 조회 부하와 쓰기 부하의 물리적 분리
  • 읽기 전용 커넥션 풀 최적화
  • 쓰기 트랜잭션의 안정성 확보
    @Bean(name = CodeVal.## + CodeVal.###)
    @JobScope
    public Step Sample6Step(JobRepository jobRepository
                          , PlatformTransactionManager platformTransactionManager
                          , @Qualifier("sqlSessionReaderFactory") SqlSessionFactory sqlSessionReaderFactory
                          , SqlSessionFactory sqlSessionFactory){

        log.info("## {} - {} Step 실행 ##", CodeVal.##, CodeVal.###);
        
        return new StepBuilder(CodeVal.## + CodeVal.###, jobRepository)
            .listener(new StepListener())
            .<Test2Dto, Test2Dto>chunk(CHUNK_SIZE_5, platformTransactionManager)
            .reader(sample6Reader(sqlSessionReaderFactory))
            .processor(sample6Processor())
            .writer(sample6Writer(sqlSessionFactory))
            .transactionManager(platformTransactionManager)
            .build();
    }

Spring Batch 에서 읽기 전용 SqlSessionFactory와 읽기-쓰기 범용 SqlSessionFactory를 사용하기

 

    @Bean
    @StepScope
    public MyBatisCursorItemReader<Test2Dto> sample6Reader(@Qualifier("sqlSessionReaderFactory") SqlSessionFactory sqlSessionReaderFactory) {
        log.info("읽기 전용 데이터소스 풀 이름 : {}", sqlSessionReaderFactory.getConfiguration().getEnvironment().getDataSource());

        return new MyBatisCursorItemReaderBuilder<Test2Dto>()
            .sqlSessionFactory(sqlSessionReaderFactory)
            .queryId("#.##.###.batch.sample.mapper.sample6.Test2Mapper.findAll")
            .parameterValues(sqlParameters())
            .saveState(false) // ExecutionContext 으로 저장하여 중단 지점에서 이어서 실행 가능 여부
            .build();
    }

Spring Batch MyBatis Item Reader 에서 읽기 전용 DataSource 에 접근하기 

 

    @Bean
    @StepScope
    public MyBatisBatchItemWriter<Test2Dto> sample6Writer(SqlSessionFactory sqlSessionFactory) {
        log.info("읽기-쓰기 범용 데이터소스 풀 이름 : {}", sqlSessionFactory.getConfiguration().getEnvironment().getDataSource());

        return new MyBatisBatchItemWriterBuilder<Test2Dto>()
            .sqlSessionFactory(sqlSessionFactory)
            .statementId("#.##.###.batch.sample.mapper.sample6.Test2Mapper.updateTest2")
            .itemToParameterConverter(item -> {
                Map<String, Object> parameter = new HashMap<>();
                parameter.put("test2Id", item.getTest2Id());
                parameter.put("testNo", item.getTestNo());
                parameter.put("note", item.getNote());
                return parameter;
            })
            .build();
    }


    @Bean
    public Map<String, Object> sqlParameters() {
        Map<String, Object> map = new HashMap<>();
        map.put("note", "init");
        return map;
    }

Spring Batch MyBatis Item Writer 에서 읽기-쓰기 범용 DataSource 에 접근하기 

 

 

JPA ItemReader / ItemWriter 설정 분리

JPA 기반 처리에서도 동일한 개념이 적용됩니다.

  • ItemReader : readerEntityManagerFactory
  • ItemWriter : EntityManagerFactory

각 설정의 역할은 다음과 같습니다.

  • readerEntityManagerFactory
    • 조회 전용 EntityManager 생성
    • 읽기 전용 DB 또는 Read Replica 연결
  • EntityManagerFactory
    • 읽기-쓰기 범용 EntityManager 생성
    • 트랜잭션 기반 영속성 컨텍스트 관리

 

Spring Batch는 ItemReader와 ItemWriter가 사용하는 EntityManager가 동일할 필요가 없도록 설계되어 있으며, 이를 통해 JPA 환경에서도 읽기/쓰기 부하 분리가 가능합니다.이 방식은 특히 다음과 같은 환경에서 효과적입니다.

  • 대량 조회 + 대량 적재 배치 
  • 데이터베이스 확장(Scale-out)이 필요한 환경
    @Bean(name = CodeVal.## + CodeVal.###)
    @JobScope
    public Step Sample5Step(JobRepository jobRepository
        , JpaTransactionManager jpaTransactionManager
        , EntityManagerFactory entityManagerFactory
        , @Qualifier("readerEntityManagerFactory") EntityManagerFactory readerEntityManagerFactory){
        log.info("Sample5JobConfig^^Sample5Job^^Sample5Step :: started ... ");

        log.info("## {} - {} Step 실행 ##", CodeVal.##, CodeVal.###);

        return new StepBuilder(CodeVal.## + CodeVal.###, jobRepository)
            .listener(new StepListener())
            .<Sample5Dto.testDto, TestEntity>chunk(CHUNK_SIZE_1000, jpaTransactionManager)
            .reader(sample5QuerydslPagingItemReader(readerEntityManagerFactory))
            .processor(sample5ItemProcessor())
            .writer(sample5JpaItemWriter(entityManagerFactory))
            .transactionManager(jpaTransactionManager)
            .build();
    }

 Spring Batch 에서 읽기 전용 EntityManagerFactory 와 읽기-쓰기 범용 EntityManagerFactory 를 사용하기 

    @Bean(name="sample5QuerydslPagingItemReader")
    @StepScope
    public QuerydslPagingItemReader<Sample5Dto.testDto> sample5QuerydslPagingItemReader(@Qualifier("readerEntityManagerFactory")
                                                                                EntityManagerFactory readerEntityManagerFactory) {

        return new QuerydslPagingItemReader<Sample5Dto.testDto>
            (readerEntityManagerFactory
                , CHUNK_SIZE_1000 // page size (chunk size)
                , queryFactory -> queryFactory
                    .select(Projections.fields(Sample5Dto.testDto.class,
                        testEntity.testId
                        , testEntity.testNo
                        , testEntity.note
                        ))
                    .from(testEntity)
                    .where(testEntity.note.eq("init"))
                    .orderBy(testEntity.createdDatetime.desc())
            );
    }

 Spring Batch JPA Item Reader 에서 읽기 전용 DataSource 에 접근하기 

    @Bean
    @StepScope
    public JpaItemWriter<TestEntity> sample5JpaItemWriter(EntityManagerFactory entityManagerFactory) {

        return new JpaItemWriterBuilder<TestEntity>()
            .entityManagerFactory(entityManagerFactory)
            .usePersist(true)
            .build();
    }

 

Spring Batch JPA Item Writer 에서 읽기-쓰기 범용 DataSource 에 접근하기 



[다이어그램1] 스프링배치 데이터소스와 읽기-쓰기 작업에 따른 트랜잭션 분리

 

 

실무적 고려사항 및 팁

메타데이터 테이블 스키마 관리 시 고려사항

Spring Batch Reference Manual에 따르면, 메타데이터 테이블은 Spring Batch가 제공하는 스키마 스크립트를 사용하여 생성합니다. 메타데이터 테이블은 BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_STEP_EXECUTION, BATCH_JOB_EXECUTION_CONTEXT, BATCH_STEP_EXECUTION_CONTEXT 등으로 구성됩니다.

 

메타데이터 테이블 스키마 관리를 위해 다음 사항을 고려해야 합니다. 메타데이터 테이블은 별도의 데이터베이스에 생성하므로, 스키마 버전 관리와 마이그레이션 전략을 수립해야 합니다. 메타데이터 테이블의 데이터가 계속 증가하므로, 오래된 실행 이력을 정리하는 정책을 수립해야 합니다. 메타데이터 데이터베이스의 백업 전략을 수립하여, 장애 발생 시 복구할 수 있도록 해야 합니다.

트랜잭션/데이터소스 분리 시 Pitfall

트랜잭션과 데이터소스를 분리할 때 주의해야 할 사항은 다음과 같습니다. ItemReader와 ItemWriter가 메타데이터 데이터소스를 사용하지 않도록 주의해야 합니다. @Qualifier를 사용하여 명시적으로 데이터소스를 지정하지 않으면, 기본 데이터소스가 사용될 수 있습니다.

 

Step의 트랜잭션 매니저를 명시적으로 지정하지 않으면, 메타데이터 트랜잭션 매니저가 사용될 수 있습니다. 이 경우 비즈니스 데이터 처리와 메타데이터 저장이 같은 트랜잭션으로 묶여, 비즈니스 데이터 롤백 시 메타데이터도 롤백될 수 있습니다. 따라서 Step의 transactionManager()를 통해 비즈니스 데이터 트랜잭션 매니저를 명시적으로 지정해야 합니다.

복구/재시작 시 ExecutionContext 연관 포인트

Spring Batch Reference Manual에 따르면, 재시작 가능한 작업은 ExecutionContext에 저장된 상태 정보를 기반으로 마지막 처리 지점부터 재개합니다. ExecutionContext는 메타데이터 데이터소스에 저장되므로, 메타데이터 데이터소스의 가용성이 재시작 가능성에 영향을 미칩니다.

 

복구/재시작 시 고려해야 할 사항은 다음과 같습니다. 메타데이터 데이터소스가 장애로 인해 사용 불가능한 경우, 작업 재시작이 불가능합니다. 따라서 메타데이터 데이터소스의 고가용성을 보장해야 합니다. ExecutionContext에 저장된 상태 정보가 손상되거나 손실된 경우, 작업 재시작이 불가능하거나 예상과 다르게 동작할 수 있습니다. 따라서 메타데이터 데이터소스의 백업과 복구 전략을 수립해야 합니다.

 

맺음말 및 요약

이 글에서는 Spring Batch 5에서 메타데이터 관리와 Reader-Writer 데이터소스 분리 방법을 설명했습니다. Spring Batch 공식 모델에 따르면, 메타데이터는 JobRepository를 통해 관리되며, JobExecution, StepExecution, ExecutionContext 등의 정보를 저장합니다.

 

메타데이터와 데이터소스 분리 시 핵심 체크 포인트는 다음과 같습니다. @EnableBatchProcessing의 dataSourceRef와 transactionManagerRef 속성을 통해 메타데이터 전용 데이터소스와 트랜잭션 매니저를 설정해야 합니다. ItemReader와 ItemWriter는 @Qualifier를 사용하여 비즈니스 데이터소스를 명시적으로 지정해야 합니다. Step의 transactionManager()를 통해 비즈니스 데이터 트랜잭션 매니저를 명시적으로 지정해야 합니다.

 

이러한 설정을 통해 메타데이터와 비즈니스 데이터의 트랜잭션을 분리하여 관리할 수 있으며, 각 데이터소스의 성능과 무결성을 독립적으로 보장할 수 있습니다. 대규모 배치 시스템에서 이러한 분리는 특히 중요하며, 안정적이고 효율적인 배치 작업을 구성하는 데 필수적입니다.