Skip to content

Tech SpringBatch

wjkim9 edited this page Jul 23, 2025 · 14 revisions

Spring Batch

왜 이 기술을 사용했는가?

뉴스 데이터를 외부 API로부터 수집하여 데이터베이스에 저장하는 과정에서, 초기에는 아래와 같은 구조를 사용했습니다:

  • API에서 한 번에 100건의 데이터를 가져옴
  • JPA를 통해 100건을 각각 DB에 insert
  • 결과적으로 API 호출 한 번마다 DB에 100번의 insert 쿼리 발생 → 매우 비효율적

이를 개선하기 위해 Spring Batch를 도입하여 다음과 같은 구조로 변경했습니다:

  • 섹션별로 API 데이터를 최대 10,000건까지 메모리에 적재
  • 적재된 데이터를 한 번에 Bulk Insert 처리
  • 처리 효율 및 속도가 크게 개선됨

또한, Spring Batch는 다음과 같은 장점을 제공하기 때문에 채택하게 되었습니다:

  • 대용량 데이터에 적합한 Chunk 기반 처리
  • Job/Step 재시작, 에러 복구, 트랜잭션 관리 등 안정적인 일괄 처리 지원
  • Spring 생태계와의 높은 통합성

다른 대안은 무엇이었는가?

대안 방식 단점 요약
@Scheduled + 서비스 호출 재시작 불가, 트랜잭션 처리 복잡, 예외 복구 불안정
Quartz 스케줄링에 강점 있으나 대량 데이터 배치에 필요한 구조는 직접 구현 필요
Java 기본 스케줄러 (Timer, ExecutorService)는 복잡한 작업 흐름 관리에 적합하지 않음

위 대안들은 모두 대규모 배치 처리에서는 상태 추적, 장애 복구, 트랜잭션 관리 측면에서 신뢰도가 떨어졌습니다.

이 기술을 사용해서 얻은 이득과 손해

구분 내용
✅ 이득 10,000건 이상 데이터를 한 번에 처리 → DB 액세스 최소화로 성능 약 27% 향상
✅ 이득 안정적인 트랜잭션 관리, 실패 시 재시도, Skip 처리 등 생산성 및 안정성 향상
✅ 이득 Job, Step, Execution 등 실행 흐름 추적 가능 → 유지보수 용이
⚠️ 손해 러닝 커브 존재, 설정이 복잡함 (JobBuilder, StepBuilder, Chunk Size 등 고려 필요)
⚠️ 손해 실시간 처리에는 부적합 (주기적 일괄 처리 전용 구조)

성능 비교

처리 방식 전체 처리 시간 (초)
기존 JPA 반복 삽입 방식 769.19
Spring Batch 일괄 삽입 방식 562.43

→ 약 206초(≈27%) 단축, 같은 데이터 양을 더 빠르고 안정적으로 처리할 수 있게 되었음.


처리 구조 설명

Spring Batch는 다음과 같은 구성으로 작동합니다:


Job → Step → Reader → Processor → Writer

  • Reader: API 또는 DB로부터 데이터를 읽어옴
  • Processor: 중복 제거, 전처리, 필터링 등 수행
  • Writer: 가공된 데이터를 일괄 Insert (현재는 JPA 기반)

예시 플로우:

  1. API로부터 섹션별 뉴스 10,000건 수집
  2. Processor에서 중복 필터링
  3. Writer에서 RDBMS 또는 Elasticsearch에 일괄 저장

→ 이 모든 흐름은 배치 Job으로 실행되며, 수동 트리거나 스케줄(Cron)로 주기 실행 가능


1. "Batch 구성 예시" 섹션에 Job/Step 정의 코드 삽입

Batch Job 구성 예시

뉴스 수집 배치는 Job-Step 구조로 구성되며, 한 번의 실행으로 최대 10,000건까지 뉴스 데이터를 수집 및 저장합니다.

@Bean
public Job newsDataSaveJob(JobRepository jobRepository,
                           Step newsDataSaveStep,
                           BatchJobCompletionListener listener) {
    return new JobBuilder("newsDataSaveJob", jobRepository)
            .start(newsDataSaveStep)
            .listener(listener)
            .build();
}

@Bean
public Step newsDataSaveStep(JobRepository jobRepository,
                             PlatformTransactionManager transactionManager,
                             ItemReader<NewsItemDTO> apiReader,
                             ItemProcessor<NewsItemDTO, News> newsProcessor,
                             ItemWriter<News> newsWriter) {

    return new StepBuilder("newsDataSaveStep", jobRepository)
            .<NewsItemDTO, News>chunk(10_000, transactionManager)
            .reader(apiReader)
            .processor(newsProcessor)
            .writer(newsWriter)
            .build();
}

위와 같이 구성된 Job은 Reader → Processor → Writer 순으로 동작하며, 10,000건씩 일괄 처리됩니다.


2. "API 기반 ItemReader" 섹션에 apiReader 일부 삽입

API 기반 뉴스 Reader 구성

@StepScope
@Bean
public ItemReader<NewsItemDTO> apiReader(
        @Value("#{jobParameters['section']}") String section,
        @Value("#{jobParameters['offset']}") Long offset
) {
    // 하루 전 날짜 구하기
    LocalDate day = LocalDate.now().minusDays(1);
    String date = day.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

    List<NewsItemDTO> newsList = new ArrayList<>();
    NewsResponseDTO newsResponseDTO = getAPIResponse(1, section, 100, date, date);
    int totalPages = getTotalPagesOfResponse(newsResponseDTO, offset);

    for (int page = 1; page <= totalPages; page++) {
        getNewsList(page, section, 100, date, date, newsList);
    }

    return new ListItemReader<>(newsList);
}

모든 페이지의 뉴스를 미리 가져와 메모리에 적재하고, 이후 하나씩 읽어가는 구조입니다. 실시간 API 호출을 피하고, 대량 데이터를 한 번에 처리할 수 있도록 구성했습니다.


3. "Writer 구조" 섹션에 JDBC Writer 삽입

JDBC Batch 기반 Writer 구성

@Bean
public ItemWriter<News> newsWriter(DataSource dataSource) {
    JdbcBatchItemWriter<News> writer = new JdbcBatchItemWriter<>();
    writer.setDataSource(dataSource);
    writer.setSql("""
        INSERT INTO news (title, summary, content_url, published_at, send, sections, publisher)
        VALUES (:title, :summary, :contentUrl, :publishedAt, :send, :sections, :publisher)
    """);
    writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
    writer.afterPropertiesSet();
    return writer;
}

JPA 대신 JdbcBatchItemWriter를 사용하여 실제 DB 삽입 쿼리를 직접 실행합니다. 이를 통해 JPA의 성능 병목을 피하고, 10,000건 일괄 삽입이 가능해졌습니다.


전체 흐름 요약

Start
↓
\[Reader]

* 뉴스 API 호출
* 섹션별 전체 데이터 수집
* ListItemReader에 저장

  ↓
  \[Processor]
* 중복/결측 필터링

  ↓
  \[Writer]
* 10,000건씩 JDBC 일괄 INSERT
* RDBMS 저장 완료

✅ 요약 정리

섹션 추천 코드 예시 위키 목적
Job/Step 정의 newsDataSaveJob, newsDataSaveStep 구조 설명 및 chunk 처리 흐름 강조
Reader 구성 apiReader, ListItemReader 도메인 기반 뉴스 수집 로직 시각화
Writer 구성 JdbcBatchItemWriter 성능 최적화 및 JPA 대비 장점 부각

개선 사항

항목 설명
Writer 최적화 현재 JPA ItemWriter 사용 → JdbcBatchItemWriter 로 변경 시 성능 향상 가능
Job 시각화 Spring Batch Admin, Spring Boot Actuator, Dashboard 도입 고려
병렬 처리 멀티스레드 기반 Step 병렬 처리로 처리 시간 단축 가능
동적 파라미터 처리 날짜, 섹션 등 동적 파라미터 기반 Job 실행 기능 추가 고려

핵심 요약

  • 기존 방식: API 100건 호출 → JPA 100번 insert → 비효율적
  • 개선 방식: 최대 10,000건 적재 후 일괄 insert → DB 액세스 횟수 대폭 감소
  • 결과: 처리 속도 및 자원 사용량 개선, 운영 안정성 향상

Clone this wiki locally