Spring Batch 파티셔닝 기반 유사도 계산 템플릿
Spring Batch의 파티셔닝을 활용한 유사도 계산 배치 작업에 대한 상세 가이드입니다.
요약
- Spring Batch의 파티셔닝을 통해 데이터 처리 효율성을 향상할 수 있습니다.
- 배치 스텝에서 유사도 계산을 위한 코드 구조와 구성 요소를 명확히 이해할 수 있습니다.
- 성능 최적화를 위한 설정 및 트랜잭션 관리 방법에 대해 배울 수 있습니다.
배경/문제
유사도 계산은 데이터 분석 및 머신러닝 분야에서 널리 사용되는 작업이며, 대량의 데이터를 처리하기 위해 효율적인 배치 프로세스가 필수적입니다. 데이터의 양이 많을 경우, 단일 스레드로 처리하는 것은 비효율적이며 성능 저하를 초래할 수 있습니다. Spring Batch는 이러한 문제를 해결하기 위해 설계된 강력한 프레임워크로, 파티셔닝 기능을 통해 데이터를 여러 섹션으로 나누어 병렬로 처리할 수 있는 기능을 제공합니다. 이 문서는 유사도 계산을 위한 Spring Batch의 파티셔닝 기반 구현 방법을 상세히 다룹니다.
접근/해결 전략
문서에서 설명하는 유사도 계산 배치 작업은 크게 다음과 같은 단계로 구성됩니다:
- 마스터 스텝을 통해 데이터를 파티션으로 나눕니다.
- 각 파티션은 독립적으로 Worker 스텝에서 병렬로 처리됩니다.
- 데이터의 읽기, 처리, 쓰기와 같은 각 단계에서 세심한 Bean 구성을 요구합니다.
이러한 과정을 체계적으로 문서화하여 다른 개발자들이 쉽게 이해하고 활용할 수 있도록 합니다.
구현 포인트
1. 아키텍처 구조
전반적인 아키텍처는 마스터 스텝을 통해 파티셔너를 사용하여 데이터를 N개의 파티션으로 분할하고, 각 파티션은 TaskExecutor를 통해 병렬로 처리됩니다. 다음은 전체 아키텍처의 개요입니다.
┌─────────────────────────────────────────────────────────────┐
│ Master Step │
│ - 파티셔너를 사용하여 데이터를 N개 파티션으로 분할 │
│ - TaskExecutor를 통해 병렬 처리 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────┬──────────────┬──────────────┬──────────────┐
│ Worker #1 │ Worker #2 │ Worker #3 │ Worker #N │
│ 파티션 1 │ 파티션 2 │ 파티션 3 │ 파티션 N │
│ 처리 │ 처리 │ 처리 │ 처리 │
└──────────────┴──────────────┴──────────────┴──────────────┘
2. Steps 클래스
유사도 계산 스텝을 정의하고 필요한 Configuration Bean을 연결합니다. 이 클래스는 Spring Batch의 기본 구조를 따르며, 유사도 계산 스텝 및 삭제 플래그 업데이트 스텝을 포함합니다.
@Component("{domain}SimilaritiesSteps")
@RequiredArgsConstructor
@Slf4j
public class {Domain}SimilaritiesSteps {
private final ApplicationContext applicationContext;
// 유사도 계산 Step
public Step calculate{Domain}SimilaritiesStep(JobRepository jobRepository, Date lastUpdatedAt) {
return applicationContext.getBean("calculate{Domain}SimilaritiesStep", Step.class);
}
// 삭제 플래그 갱신 Step
public Step update{Domain}SimilaritiesDelYnStep(JobRepository jobRepository, Date lastUpdatedAt) {
return new Update{Domain}SimilaritiesDelYnStep(...).build();
}
}
3. StepsConfig 클래스
파티셔닝 관련 모든 Bean을 정의하는 설정 파일입니다. 이 구성은 각 단계에서 필요한 Reader, Processor, Writer 등을 설정하여 유기적으로 작동하게 합니다.
@Bean
@StepScope
public JpaPagingItemReader<Object[]> {domain}SimilaritiesPartitionReader(
@Value("#{stepExecutionContext['startBoundary']}") Long startBoundary,
@Value("#{stepExecutionContext['endBoundary']}") Long endBoundary,
@Value("#{stepExecutionContext['partitionNumber']}") Integer partitionNumber) {
// 파티션 범위에 따른 쿼리 선택
String queryString = (endBoundary == null)
? BatchSql.{DOMAIN}_SIMILARITIES_DATA_LAST
: BatchSql.{DOMAIN}_SIMILARITIES_DATA;
return new JpaPagingItemReaderBuilder<Object[]>()
.name("{domain}SimilaritiesPartitionReader-" + partitionNumber)
.entityManagerFactory(syworksEntityManagerFactory)
.queryString(queryString)
.parameterValues(params)
.pageSize(PAGE_SIZE)
.saveState(false) // 멀티스레드 환경에서 restart 충돌 방지
.build();
}
4. SQL 쿼리 패턴
배치 작업에 사용할 SQL 쿼리는 유사도 계산에 필수적이며, 각 도메인별로 구성됩니다.
1. COUNT 쿼리
-- {DOMAIN}_SIMILARITIES_COUNT
SELECT COUNT(*)
FROM (
-- 유사도 계산 대상 서브쿼리
) AS subquery
2. 파티션 경계 쿼리
-- {DOMAIN}_SIMILARITIES_PARTITION_BOUNDARY
WITH candidates AS (
-- 처리 대상 ID 추출
),
boundary_data AS (
SELECT
id,
NTILE(?1) OVER (ORDER BY id) AS partition_num,
ROW_NUMBER() OVER (ORDER BY id) AS global_row_num
FROM candidates
),
partition_ranges AS (
SELECT
partition_num,
MIN(id) AS min_id,
MAX(id) AS max_id
FROM boundary_data
GROUP BY partition_num
)
SELECT
p1.partition_num as partitionNum,
p1.min_id as startBoundary,
p2.min_id as endBoundary
FROM partition_ranges p1
LEFT JOIN partition_ranges p2
ON p1.partition_num = p2.partition_num - 1
ORDER BY p1.partition_num
3. 데이터 조회 쿼리 (범위 포함)
-- {DOMAIN}_SIMILARITIES_DATA
SELECT
keywordId,
targetId,
AVG(cosineSimilarity) as cosineSimilarity,
AVG(euclideanDistance) as euclideanDistance,
AVG(manhattanDistance) as manhattanDistance
FROM ...
WHERE id >= :startBoundary AND id < :endBoundary
GROUP BY keywordId, targetId
5. Service 레이어 요구사항
유사도에 대한 Bulk UPSERT 메서드는 다음과 같이 정의됩니다. 이 메서드는 데이터 유효성을 검증하고 데이터베이스에 효율적으로 업데이트합니다.
@Transactional(transactionManager = "syworksTransactionManager", propagation = Propagation.REQUIRES_NEW)
public int bulkUpsert{Domain}Similarities(List<{Domain}SimilaritiesDto> similarities) {
String sql = String.format("""
INSERT INTO %s.{table_name} (
keyword_id, target_id,
cosine_similarity, euclidean_distance, manhattan_distance,
del_yn, last_batch_date, last_batch_time
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (keyword_id, target_id)
DO UPDATE SET
cosine_similarity = EXCLUDED.cosine_similarity,
euclidean_distance = EXCLUDED.euclidean_distance,
manhattan_distance = EXCLUDED.manhattan_distance,
del_yn = EXCLUDED.del_yn,
last_batch_date = EXCLUDED.last_batch_date,
last_batch_time = EXCLUDED.last_batch_time
""", SCHEMA_NAME);
// PreparedStatement를 사용한 배치 처리
// 500건씩 executeBatch() 실행
}
주의사항/트레이드오프
- 트랜잭션 관리
- Bulk UPSERT는
REQUIRES_NEW전파 수준을 사용하여 별도로 관리해야 합니다. DDL 명령어는 별도 트랜잭션으로 실행할 필요가 있습니다.
- Bulk UPSERT는
- 메모리 관리
- 청크 크기는 기본적으로 5000으로 설정하며, 메모리와 처리 속도 간의 균형을 유지해야 합니다. Reader의
saveState(false)설정은 멀티스레드 환경에서의 충돌을 방지하기 위해 필수적입니다.
- 청크 크기는 기본적으로 5000으로 설정하며, 메모리와 처리 속도 간의 균형을 유지해야 합니다. Reader의
- 병렬 처리
- 파티션 수와 스레드 풀 크기를 동일하게 설정하여 CPU 코어 수를 고려하여 최적의 성능을 유지해야 합니다.
- 로깅
- 처리 진행 상황에 대한 로그를 통해 작업 상태를 모니터링할 수 있으며, 파티션별 독립적인 로깅이 필요합니다.
마무리
새로운 유사도 계산 스텝을 구현하기 위해 아래 체크리스트를 확인해야 합니다:
- Entity & DTO 준비
- JPA Entity 클래스와 DTO 클래스 생성.
- Repository 구현
- JpaRepository 인터페이스와 RepositorySupport 클래스 생성 및 적절한 메서드 구현.
- Service 레이어 구성
- Service 인터페이스와 구현체 생성.
- 배치 SQL 쿼리 추가
- COUNT, PARTITION_BOUNDARY, DATA 쿼리 추가.
- Step 및 Config 설정
- Steps 클래스 및 StepsConfig 클래스 생성하여 필요한 Bean 정의.
- Job 등록
- JobLaunchers에 새로운 Step 추가.
이러한 구성을 통해 Spring Batch를 활용한 유사도 계산 배치 작업을 효율적으로 설계하고 구현할 수 있습니다.