배치 작업에서 Redis Lock으로 멱등성 잡은 과정
배치 작업에서 Redis Lock으로 멱등성 잡은 과정
LMS에는 회원권 배치가 있었다. ERP 시스템에서 신규 회원권 데이터를 가져와 자체 DB에 반영하는 작업이었다.
그런데 어느 날부터 가끔 회원권이 2개씩 생기는 문제가 보고됐다. 처음엔 단순 데이터 오입력이겠거니 했는데 재현 패턴을 보니 아니었다. 특정 시간대에만 발생했고, 데이터를 보면 완전히 동일한 회원권이 두 번 삽입돼 있었다.
왜 중복이 생겼나: 레이스 컨디션
배치 구조는 이랬다.
// 기존 배치 구조 (단순화)
@Scheduled(fixedDelay = 60000)
public void syncMembership() {
List<MembershipDto> erpData = erpClient.fetchNewMemberships();
for (MembershipDto dto : erpData) {
boolean exists = membershipRepository.existsByErpId(dto.getErpId());
if (!exists) {
membershipRepository.save(dto.toEntity()); // 신규 삽입
}
}
}코드만 보면 문제가 없어 보인다. existsByErpId로 중복을 체크하고 없을 때만 저장한다. 문제는 이 배치가 여러 스레드에서 동시에 실행될 수 있다는 거였다.
스레드 A: existsByErpId("ERP-001") → false (없음)
스레드 B: existsByErpId("ERP-001") → false (없음) ← 아직 A가 저장 전
스레드 A: save("ERP-001")
스레드 B: save("ERP-001") ← 중복 삽입
exists 확인과 save 사이의 짧은 간격에 다른 스레드가 들어와 똑같이 "없음"을 확인하면, 둘 다 삽입한다.
왜 이 시점에 불거졌나
배치가 처음 만들어질 때는 단일 스레드로 돌았다. 그러다 ERP에 신규 상품 카테고리가 추가되면서 처리할 데이터가 늘었고, 속도를 올리려고 멀티스레드를 적용했다. 단순히 멀티스레드로 돌리면 속도가 빨라지겠지 하고 생각한 주니어 때의 패착이었다.
Redis 분산 락으로 해결
DB에 UNIQUE 제약을 걸면 간단했겠지만, 다른 부서와 같이 쓰는 공유 오라클 DB라 우리 마음대로 제약을 걸 수 없었다. 그래서 애플리케이션 레벨에서, 중복 요청을 DB에 닿기 전에 걸러내는 Redis 분산 락으로 갔다.
public void syncMembership(String erpId) {
String lockKey = "membership:lock:" + erpId;
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "locked", 30, TimeUnit.SECONDS); // NX + EX
if (!acquired) {
return; // 이미 다른 스레드가 처리 중
}
try {
boolean exists = membershipRepository.existsByErpId(erpId);
if (!exists) {
membershipRepository.save(new Membership(erpId));
}
} finally {
redisTemplate.delete(lockKey); // 처리 완료 후 락 해제
}
}SETNX(Set if Not Exists)는 원자적 연산이라 두 스레드가 동시에 실행해도 하나만 성공한다. 락을 못 잡은 스레드는 DB에 접근조차 안 하고, 30초 TTL을 걸어둬서 처리 중에 서버가 죽어도 락이 자동으로 풀린다.
멱등성은 이중으로 보장했다. 락과 별개로 existsByErpId 체크를 남겨서, 락이 만료되는 드문 경우에도 "없으면 삽입, 있으면 무시"가 지켜진다. 배치가 몇 번을 실행되든 결과가 같다.
결과
고치고 나서 중복 회원권은 0건이 됐다. 처리 도중 서버가 죽어도 TTL이 지나면 락이 풀려 다음 배치가 이어받는다.
이번에 느낀 점은, 동시에 작업을 진행할 때 공통된 자원에 접근하며 생기는 레이스 컨디션을 몰랐다는 것이다. 동시성 작업에는 항상 레이스 컨디션이 따라오고, 공통 자원을 컨트롤할 때는 중앙에서 원자성이 보장되는 매체를 쓰는 게 좋다는 경험을 얻었다.