Diary/TIL

2024-05-12) 동시성 이슈, 락에 대한 고찰

nsean 2024. 5. 24. 15:44

우리 프로젝트는 시간관계상 비관적 락을 통해서 구현을 시작했었고, 비관적 락을 사용하기에, 로드 밸런싱을 사용했을때 서버에 가해지는 부하는 분산됐지만, 처리 총량은 변하지 않았다.

대용량 트래픽을 처리해야 하는 환경에서는 비관적 락과 낙관적 락 모두 한계가 있을 수 있다. 대규모 트래픽과 데이터 처리에서는 두 가지 락 기법 모두 성능 저하나 확장성 문제를 초래할 수 있기 때문이다.

비관적 락의 한계

  1. 성능 저하
    많은 트랜잭션이 동시에 락을 획득하려고 하면 경합이 발생해 성능이 저하될 수 있다.
  2. 확장성 문제
    락 관리의 복잡성이 증가하면서 시스템 확장성에 한계가 생긴다.
  3. 데드락 발생 가능성
    여러 트랜잭션이 동시에 여러 자원을 락하려고 하면 데드락 가능성이 높아진다.

낙관적 락의 한계

  1. 높은 충돌 발생 가능성
    트랜잭션 수가 많아지면 충돌이 자주 발생할 수 있고, 이로 인해 롤백과 재시도가 빈번해지면서 성능이 저하될 수 있다.
  2. 복잡한 충돌 해결
    충돌이 발생했을 때 이를 해결하기 위한 롤백, 재시도 등의 작업이 복잡해질 수 있다.

대안 방법

  1. 분산 락 서비스
    • 예시: Zookeeper, Consul
    • 장점: 락을 중앙에서 관리하고 조정하여 락 관리의 일관성을 유지하고, 다중 서버 환경에서의 락 경합 문제를 완화할 수 있다.
  2. 이벤트 기반 아키텍처
    • CQRS: Command Query Responsibility Segregation 패턴을 도입해, 데이터 변경을 이벤트로 처리하고, 읽기와 쓰기 작업을 분리해 성능을 향상시킬 수 있다.
  3. 샤딩 (Sharding)
    • 방법: 데이터를 여러 서버에 분산 저장하고, 트래픽을 분산 처리해 성능을 향상시킬 수 있다.
    • 장점: 각 샤드가 독립적으로 락을 관리할 수 있어 락 경합 문제를 줄인다.
  4. NoSQL 데이터베이스
    • 예시: Cassandra, MongoDB
    • 장점: 고성능, 고확장성을 제공하며, 복잡한 락 관리 대신 단순한 동시성 제어 기법을 사용한다.
  5. 메시지 큐 (Message Queue)
    • 예시: RabbitMQ, Kafka
    • 장점: 트랜잭션을 비동기적으로 처리하고, 시스템 부하를 분산시킬 수 있어 대용량 트래픽을 효과적으로 처리할 수 있다.

결론

비관적 락과 낙관적 락 모두 대용량 트래픽 환경에서는 한계가 있다. 따라서, 보다 효율적인 동시성 제어와 시스템 확장을 위해서는 분산 락 서비스, 이벤트 기반 아키텍처, 샤딩, NoSQL 데이터베이스, 메시지 큐 등의 대안 방법을 고려하는 것이 바람직하다. 이를 통해 성능 향상과 확장성을 동시에 달성할 수 있다.

프로젝트 피드백 후, 비관적 락에 대해서 찾아보다가 다음과 같은 영상을 찾아보게 되었다. 토스에서 동시성 이슈를 처리해줄 떄에도 redis 분산락을 사용한다는 이야기가 귀에 박히게 되었고, redis 분산락은 어떻게 구현하는가? 에 대하여 알아보게 되었다.

 

생각을 해봤을 때, redis 분산락을 사용함으로써 얻을 수 있는 이점은 다음과 같았다.

  1. 데이터베이스 부하 감소
    • 락 관리를 데이터베이스와 분리하여 본래의 읽기/쓰기 작업에 집중할 수 있게 하여 성능을 최적화한다.
    • 데이터베이스 내에서의 락 경합을 줄여 성능 저하를 방지한다.
  2. 확장성 및 성능 향상
    • Redis는 인메모리 데이터 저장소로, 락 설정 및 해제 작업이 매우 빠르다.
    • Redis는 클러스터링을 통해 확장 가능하여 고가용성과 확장성을 제공한다.
  3. 다중 서버 환경에서의 일관성 보장
    • 중앙 집중식 락 관리로 다중 서버 환경에서도 데이터 일관성을 유지할 수 있다.
    • 세션 고정이 필요 없으며, 모든 서버가 동일한 락 정보를 공유한다.
  4. 간편한 구현 및 유지보수
    • 간단한 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 분산 락은 고성능, 고확장성 시스템에서 매우 유용하다.