우리 프로젝트는 시간관계상 비관적 락을 통해서 구현을 시작했었고, 비관적 락을 사용하기에, 로드 밸런싱을 사용했을때 서버에 가해지는 부하는 분산됐지만, 처리 총량은 변하지 않았다.
대용량 트래픽을 처리해야 하는 환경에서는 비관적 락과 낙관적 락 모두 한계가 있을 수 있다. 대규모 트래픽과 데이터 처리에서는 두 가지 락 기법 모두 성능 저하나 확장성 문제를 초래할 수 있기 때문이다.
비관적 락의 한계
- 성능 저하
많은 트랜잭션이 동시에 락을 획득하려고 하면 경합이 발생해 성능이 저하될 수 있다. - 확장성 문제
락 관리의 복잡성이 증가하면서 시스템 확장성에 한계가 생긴다. - 데드락 발생 가능성
여러 트랜잭션이 동시에 여러 자원을 락하려고 하면 데드락 가능성이 높아진다.
낙관적 락의 한계
- 높은 충돌 발생 가능성
트랜잭션 수가 많아지면 충돌이 자주 발생할 수 있고, 이로 인해 롤백과 재시도가 빈번해지면서 성능이 저하될 수 있다. - 복잡한 충돌 해결
충돌이 발생했을 때 이를 해결하기 위한 롤백, 재시도 등의 작업이 복잡해질 수 있다.
대안 방법
- 분산 락 서비스
- 예시: Zookeeper, Consul
- 장점: 락을 중앙에서 관리하고 조정하여 락 관리의 일관성을 유지하고, 다중 서버 환경에서의 락 경합 문제를 완화할 수 있다.
- 이벤트 기반 아키텍처
- CQRS: Command Query Responsibility Segregation 패턴을 도입해, 데이터 변경을 이벤트로 처리하고, 읽기와 쓰기 작업을 분리해 성능을 향상시킬 수 있다.
- 샤딩 (Sharding)
- 방법: 데이터를 여러 서버에 분산 저장하고, 트래픽을 분산 처리해 성능을 향상시킬 수 있다.
- 장점: 각 샤드가 독립적으로 락을 관리할 수 있어 락 경합 문제를 줄인다.
- NoSQL 데이터베이스
- 예시: Cassandra, MongoDB
- 장점: 고성능, 고확장성을 제공하며, 복잡한 락 관리 대신 단순한 동시성 제어 기법을 사용한다.
- 메시지 큐 (Message Queue)
- 예시: RabbitMQ, Kafka
- 장점: 트랜잭션을 비동기적으로 처리하고, 시스템 부하를 분산시킬 수 있어 대용량 트래픽을 효과적으로 처리할 수 있다.
결론
비관적 락과 낙관적 락 모두 대용량 트래픽 환경에서는 한계가 있다. 따라서, 보다 효율적인 동시성 제어와 시스템 확장을 위해서는 분산 락 서비스, 이벤트 기반 아키텍처, 샤딩, NoSQL 데이터베이스, 메시지 큐 등의 대안 방법을 고려하는 것이 바람직하다. 이를 통해 성능 향상과 확장성을 동시에 달성할 수 있다.
프로젝트 피드백 후, 비관적 락에 대해서 찾아보다가 다음과 같은 영상을 찾아보게 되었다. 토스에서 동시성 이슈를 처리해줄 떄에도 redis 분산락을 사용한다는 이야기가 귀에 박히게 되었고, redis 분산락은 어떻게 구현하는가? 에 대하여 알아보게 되었다.
생각을 해봤을 때, redis 분산락을 사용함으로써 얻을 수 있는 이점은 다음과 같았다.
- 데이터베이스 부하 감소
- 락 관리를 데이터베이스와 분리하여 본래의 읽기/쓰기 작업에 집중할 수 있게 하여 성능을 최적화한다.
- 데이터베이스 내에서의 락 경합을 줄여 성능 저하를 방지한다.
- 확장성 및 성능 향상
- Redis는 인메모리 데이터 저장소로, 락 설정 및 해제 작업이 매우 빠르다.
- Redis는 클러스터링을 통해 확장 가능하여 고가용성과 확장성을 제공한다.
- 다중 서버 환경에서의 일관성 보장
- 중앙 집중식 락 관리로 다중 서버 환경에서도 데이터 일관성을 유지할 수 있다.
- 세션 고정이 필요 없으며, 모든 서버가 동일한 락 정보를 공유한다.
- 간편한 구현 및 유지보수
- 간단한 API를 제공하여 락 설정 및 해제를 쉽게 구현할 수 있다.
- Redisson과 같은 라이브러리를 사용하면 더욱 쉽게 분산 락을 구현할 수 있다.
Redis 분산 락의 예제
아래는 Redisson을 사용하여 Redis 분산 락을 구현한 예제입니다.
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}
좌석 예약 서비스:
@Service
public class ReservationService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private EntityManager entityManager;
public void reserveSeat(Long seatId, Long userId) {
String lockKey = "seatLock:" + seatId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 락을 얻기 위해 대기
lock.lock();
Seat seat = entityManager.find(Seat.class, seatId);
checkIsAvailableSeat(seat);
seat.reserve();
Reservation reservation = new Reservation();
reservation.setSeat(seat);
reservation.setUserId(userId);
entityManager.persist(reservation);
} finally {
// 락 해제
lock.unlock();
}
}
private void checkIsAvailableSeat(Seat seat) {
if (!seat.isAvailable()) {
throw new SeatAlreadyReservedException("Seat is already reserved.");
}
}
}
Redis 분산 락의 작동 방식
SET 명령 사용:
SET my_lock "lock_value" NX PX 30000
NX: 키가 존재하지 않을 때만 설정
PX 30000: 키의 유효 기간을 30초로 설정
이를 통해 다른 클라이언트가 동일한 키를 설정하지 못하도록 하며, 지정된 시간 후에 자동으로 락이 해제되도록 합니다.
요약
Redis 분산 락을 사용하면 다음과 같은 주요 이점을 얻을 수 있다:
- 데이터베이스 부하 감소
- 확장성 및 성능 향상
- 다중 서버 환경에서의 일관성 보장
- 간편한 구현 및 유지보수
이러한 이점들로 인해 Redis 분산 락은 고성능, 고확장성 시스템에서 매우 유용하다.
'Diary > TIL' 카테고리의 다른 글
2024-05-16) MYSQL-MSSQL 비교 (0) | 2024.05.24 |
---|---|
2024-05-14) 프로젝트 복기 (0) | 2024.05.24 |
2024-05-08) MySQL DB Indexing 고찰 (0) | 2024.05.24 |
2024-04-29) DB Connection pool 검색 (0) | 2024.05.22 |
2024-04-22) 동시성 이슈 공부 (0) | 2024.05.22 |