주문을 받아와 처리를 하다보면 재고 처리를 위해 동시성 제어가 필요하다.
아래는 여러가지 방법을 간략하게 정리해보고 내가 선택한 방식 이유 와 장점을 정리했다.
1. Redis 큐를 이용한 직렬화 방식
Redis 리스트 자료구조를 활용해 큐를 만들어 처리
큐의 FIFO 를 이용해 LEFT PUSH 이후 RIGHT POP을 통하여 메시지를 하나씩 꺼내 처리
장점 - 비즈니스 무결성 보장, 고속 Redis 기반 처리로 성능 우수, DLQ 이용 실패한 큐의 재전송 용이
단점- Redis 장애 시 전체 시스템 영향 가능성, Redis TTL 설정 누락 시 큐가 무한히 쌓일 의험, 운영 중 큐/워크 스케쥴러 튜닝 필요
2.Synchronized, ReentrantLock (단일 인스턴스 JVM 내 락)
JAVA 자체 락을 통해 Critical Section을 직렬화
단일 서버 환경에 적합, Spring boot 단독 서버에서 간단하게 사용가능
장점 - 구현이 단순하고 추가 인프라 필요 X, JVM 내에서 완벽하게 락을 보장
단점 - 서버 인스턴스가 늘어나면 무용지물 (인스턴스별 메모리 영역 분리), 클러스터 환경에서는 동작 불가
현재 구조에 맞지 않음(멀티 서버 구조)
3. 데이터베이스 기반의 락
비관적 락(Pessimistic Lock)
데이터를 SELECT FOR UPDATE 형태로 가져와 락을 건 채 트랜잭션을 유지
동시에 접근하는 모든 트랜잭션은 대기하게됨
//ex
@Lock(LockModeType.PESSIMISTIC_WRITE)
Order findByOrderNo(String orderNo);
장점 - 경쟁이 매우 치열하거나 순서 보장이 중요한 데이터에 적합, DB 레벨에서 무조건 직렬화 보장
단점 - 트랜잭션 지연 발생, 데드락 발생 위험
낙관적 락(Optimistic Lock)
레코드를 가져올 땐 락 없이 처리
저장 시점에 version필드 등을 비교해 충돌 여부 판단
//ex
@Version
private Long version;
order.setQty(10);
orderRepository.save(order); // version mismatch 시 예외 발생
장점 - 충돌이 적은 환경에서 효율적 (대부분의 웹 시스템에 적합), 락을 잡지 않으므로 성능 유리
단점 - 충돌 발생 시 재시도 로직 필요, 실시간성이 요구되는 시스템에는 부적합
4. Redis 분산 락 (Redisson / Lettuce)
Redis SETNX 또는 Lua 스크립트를 활용해 락 키를 생성하고 만료 시간을 설정
멀티 인스턴스 환경에서 유일한 자원 접근 보장
//ex
RLock lock = redissonClient.getLock("lock:account:" + accountId);
if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {
try {
// 주문 처리
} finally {
lock.unlock();
}
}
장점 - 클러스트 환경에서의 락 보장,TTL 및 자동 만료 설정 가능
단점 - 락 획득 실패 시 로직 설계가 필요, Redis 장애 시 전체 처리 지연 가능
*락 키는 "lock:business-key" 형태로 설계하여 중복 방지
5. Kafka 파티셔닝 기반 직렬화
Kafka의 파티셔닝 기능을 활용해 동일한 accountId 가 항상 같은 Partition으로 전송되도록 설정
Consumer는 파티션 단위로 순차 소비
//ex
//consumer는 파티션 내부에서는 메시지 순서보장
ProducerRecord<String, String> record = new ProducerRecord<>("topic", accountId, payload);
장점 - 높은 확장성과 처리량( 수평 확장에 최적화), 분산환경에서도 순차 보장 가능
단점 - Kafka 운영 복잡도 증가(broker, topic, consumer group 관리), 장애 복구나 DLQ 재처리에 별도 설계 필요
* 이벤트 기반 마이크로서비스, 대용량 메시징 시스템
방법 환경 순서 보장 분산 처리 성능 복잡도
Redis 큐 | 분산 | ✔ | ✔ | ★★★★☆ | ★★☆☆☆ |
ReentrantLock | 단일 | ✔ | ✖ | ★★★☆☆ | ★☆☆☆☆ |
Pessimistic Lock | DB 트랜잭션 | ✔ | ✔ | ★★☆☆☆ | ★★☆☆☆ |
Optimistic Lock | DB, 낮은 충돌 환경 | ✖(충돌 가능성) | ✔ | ★★★★☆ | ★★☆☆☆ |
Redis 분산 락 | 멀티 인스턴스 | ✔ | ✔ | ★★★☆☆ | ★★★☆☆ |
Kafka 파티션 | 분산 이벤트 | ✔(파티션 내) | ✔ | ★★★★★ | ★★★★☆ |
현재 작업중인 서버를 고려해 1번 Redis 큐를 활용한 방법을 선택했따.
- 외부 API 호출 기반의 처리 구조
- 계정 단위 직렬화 처리 필요
- 장애 발생 시 유연한 재처리 체계 필요
- 복잡하지 않으면서도 확장 가능한 구조 지향
결과적으로 Redis 큐를 중심으로 구성한 주문 처리 워커 및 DLQ 재처리 시스템은 고립된 처리 단위를 보장하면서, 비즈니스의 신뢰성을 유지하고, 운영 편의성과 확장성까지 확보할 수 있는 적절한 선택이지 않나 싶다.
'코드 > dev' 카테고리의 다른 글
DDL 작업 중 복제지연 원인과 해결 방안 (3) | 2025.05.22 |
---|---|
도커(Docker) 명령어 (0) | 2025.04.22 |
도커(Docker) 컨테이너(Container) (1) | 2025.04.21 |
Bastion (0) | 2025.04.13 |
처리율 제한 알고리즘 (0) | 2025.04.04 |