대량 배치 안정성을 높이기 위한 구조 개선: Part 2 - 전체 삭제 후 전체 등록 구조의 위험성
2편. 전체 삭제 → 전체 등록 구조의 위험성
들어가며
1편에서 살펴본 것처럼, 대량 데이터를 처리하는 배치 작업은 어느 순간부터 “느리다”는 문제를 넘어 “위험하다”는 영역으로 들어섰다.
이번 글에서는 그 원인이 되었던 “전체 삭제 → 전체 등록” 구조가 왜 대규모 환경에서 치명적인 선택이 되는지, 그리고 어떤 관점으로 개선 목표를 설정했는지를 정리한다.
단순함이 항상 안전을 보장하지는 않는다
초기 배치 구조는 매우 직관적이었다.
- 기존 데이터 전체 삭제
- 신규 데이터 전체 등록
- 하나의 트랜잭션으로 감싸 정합성 보장
이 구조의 장점은 명확했다.
- 구현이 쉽다
- 중간 실패 시 롤백으로 데이터 정합성이 유지된다
- 비즈니스 로직을 이해하기 쉽다
하지만 데이터 규모가 커지자, 이 “단순함”은 곧 시스템 리스크로 바뀌었다.
대규모 환경에서 전체 삭제가 특히 위험한 이유
1. 삭제는 가장 무거운 연산 중 하나다
대량 삭제 쿼리는 단순히 레코드를 지우는 작업이 아니다.
- 대량 I/O 발생
- 인덱스 정리 비용
- Undo / Redo 로그 증가
- 장시간 테이블 락 점유
특히 범위 삭제가 아닌 대량 조건 삭제는 DB 입장에서 가장 부담이 큰 작업 중 하나였다.
2. “삭제가 끝나야 다음 단계로 갈 수 있는” 구조
전체 삭제가 끝나기 전까지는 등록 작업이 단 한 건도 시작되지 않는다.
이로 인해 발생한 문제는 다음과 같았다.
- 삭제가 지연되면 전체 배치가 지연됨
- 삭제 실패 시 등록 로직은 아예 실행조차 되지 않음
- 실패 원인이 삭제인지 등록인지 즉시 파악하기 어려움
즉, 삭제 단계 하나가 전체 배치의 병목이자 단일 실패 지점이 되었다.
3. 실패 비용이 지나치게 크다
단일 트랜잭션 구조에서는 배치 실행 시간이 길어질수록 실패 비용도 커진다.
- 10분 작업 실패 → 10분 손실
- 40분 작업 실패 → 40분 손실
- 재실행 시 동일한 부하가 반복
이 구조는 시스템이 커질수록 “한 번의 실패를 감당할 수 없는 상태”로 진화했다.
문제의 본질은 ‘삭제’가 아니었다
중요한 점은, 문제의 핵심이 단순히 삭제 쿼리 성능이 아니었다는 것이다.
더 근본적인 문제는 다음과 같았다.
- 모든 데이터를 한 번에 처리해야 한다는 전제
- 모든 단계를 하나의 트랜잭션으로 묶어야 한다는 사고
- 부분 성공이라는 개념이 존재하지 않는 구조
즉, 구조 자체가 대규모 데이터를 전제로 설계되지 않았다.
개선을 위한 기준 다시 세우기
구조를 바꾸기 전에, 먼저 개선 목표를 명확히 했다.
1. 실패 비용을 줄인다
- 일부 작업 실패가 전체 실패로 이어지지 않도록
- 재시도 시 이미 성공한 작업을 재사용할 수 있도록
2. 장기 트랜잭션을 제거한다
- 트랜잭션 범위를 가능한 한 짧게 유지
- 락 점유 시간을 최소화
- 다른 시스템 기능에 미치는 영향 축소
3. 처리량보다 안정성을 우선한다
- 최고 성능이 아닌 예측 가능한 성능
- 배치 실행 중에도 시스템이 안정적으로 동작하는 구조
- 운영자가 신뢰할 수 있는 실행 모델
4. 확장 가능성을 고려한다
- 데이터가 더 늘어나도 구조를 다시 뜯어고치지 않도록
- 병렬 처리, 분산 처리로 자연스럽게 확장 가능한 구조
우리가 선택한 방향
이 기준을 바탕으로 내린 결론은 명확했다.
“전체”를 한 번에 처리하지 말고, 안전한 단위로 나누어 처리하자.
- 데이터를 청크 단위로 분할
- 각 청크를 독립적으로 처리
- 실패 시 해당 청크만 재처리
- 병렬 실행이 가능하도록 구조 설계
이 선택이 이후 모든 개선의 출발점이 되었다.
다음 편 예고
다음 글에서는 이 원칙을 실제 코드와 구조에 어떻게 녹였는지, 청크 분할과 병렬 처리 기반 배치 구조 재설계 과정을 다룬다.
- 청크 크기 결정 기준
- 병렬 처리 시 주의해야 할 포인트
- 삭제 쿼리 성능을 어떻게 끌어올렸는지
👉 3편. 청크 분할과 병렬 처리로 배치 구조 재설계하기에서 이어진다.
댓글
아직 댓글이 없습니다