본문 바로가기
Spring

Redis 부숴버리기(5) 레디스를 사용한 동시성 문제 해결 - 분산 락

by windy7271 2024. 5. 16.
728x90
반응형

 

트랜잭션이 동시에 실행됐을 때 발생할 수 있는 문제 관련한 상황을 DB동시성 문제 라고 한다.

1. Dirty Read

  • 한 트랜잭션이 다른 트랜잭션이 수정 중인 데이터를 읽을 수 있는 문제
  • 아직 commit되지 않은 데이터가 읽힘으로서, 추후 rollback 될 가능성이 있는 데이터 read (실제로는 커밋을 했는데, 반영이 안됐다 라는 뜻

해결방법 : Read Committed 격리성 : 즉 커밋된 내용만 읽겠다는 뜻

 

2. Non-Repeatable Read .(UPDATE 문제)

  • mariaDB의 기본 설정은 Repeatable Read 이다
  • 한 트랜잭션에서 동일한 조회 쿼리를 두 번 이상 실행할때에, 그 중간에 다른 트랜잭션에서 데이터를 수정하여 한 트랜잭션의 결과가 다르게 나타나는 문제 예시) 재고 업데이트 전 현재 재고 조회 -> 수량업데이트 -> 변경된 재고 수량을 다시 조회(한트랜잭션에서). 그러나 만일 이를 중간에 누가 수정을 가하면 재조회시 오차 발생하여 에러.

해결방법

  • Repeatable Read 격리성 (격리 레벨을 올린다)-> 한 트랜잭션이 조회하는 동안 다른 트랜잭션이 update를 하더라도 현재 트랜잭션의 read값이 변경되지 않도록 하는 격리성. 그러나, 만약 다른 트랜잭션에서 update를 통해 값을 변경해버렸다면, read한 값 자체에 문제 발생. => 타 트랜잭션의 update를 막기 위한 select for update 쿼리 필요

3. Phantom Read (없던 데이터가 유령처럼 읽힌다. insert 문제)

  • 한 트랜잭션이 같은 조회쿼리를 여러 번 실행했을 때, 그 중간에 다른 트랜잭션에서 새로운 데이터를 추가/삭제하여 다르게 나타나는 문제

해결책 :

  • Repeatable Read 격리성.  팬텀(유령) 읽기 또한 repeatable Read격리성으로 해결이 가능하나, 이 부분은 DB마다 차이가 있어 phantom read를 해결하기 위해 Serializable 격리수준이 필요할수도 있음. 다만, DB에서 주로 발생하는 문제는 동시에 수정하는 상황이므로, 수정에 초점을 두고 해결 전략을 살펴봐도 좋을것.
  • 사실 동시성 문제는 read만이 문제는 아니고, read이후 DB에 어떠한 수정사항을 가할때도 read의 오차로 인한 또다른 오차가 발생하여 DB 전체에 영향이 발생하므로 DB 전체에 대한 동시성이라 보면 될것.

 


 

DB 격리 수준 : DB 동시성 문제를 해결하기 위한 격리수준

 

Read Uncommitted

  • 즉, 데이터가 변경되었다면, 커밋되지 않았다 하더라도 읽을 수 있도록 하는 격리수준
  • dirty read 발생 가능

Read Committed

  • 다른 트랜잭션이 커밋된 데이터만 읽을 수 있는 격리수준.
  • 다만, 나의 트랜잭션에 여러 select 문이 있을 경우에, 그 사이에 다른 트랜잭션에서 update 또는 insert 등을 발생시키고 commit하게 될시 phantom read 또는 non-repeatable-read 발생가능

Repeatable Read

  • 한 번 읽은 데이터는 같은 트랜잭션 내에서는 항상 같은 값을 갖도록 하는 격리수준 나의 트랜잭션에서 먼저 read하는 동안 다른 트랜잭션에서는 변경,추가 하더라도 같은 read값을 보장하는 것. -> Non-Repeatable ReadPhantom read를 해결
  • 실제로는 바뀌어 있지만, 신경을 안 쓰는 것이다. ⇒문제발생
  • repeatable read를 하더라도 두 가지 문제가 발생할 가능성 존재
    • 나의 트랜잭션이 read하는 동안 타 트랜잭션에서 update하게 되면 read해온 값이 달라지는 문제 발생
    • 그러나, select for update도 두 다른 트랜잭션이 동시에 read하는 것은 가능하여 lost update 문제 가능성 존재 ex)상품주문의 최종 수량이 1개 -> transaction에 read && update가 있을때 -> 내 tran에서 1 read -> 타 트랜잭션이 1 read -> 내 tran에서 0으로 update -> 타 tran에서 0으로 update -> 최종 수량에 오류 발생

Serializable

  • 동시에 실행되는 여러 트랜잭션들을 순차적으로 실행한 것과 같은 결과를 보장 -> 즉 데이터베이스 차원에서 동시에 특정 데이터에 접근하는 것을 차단
  • 단점 느리거나, 한 사용자만 사용하게 된다.

 

REPEATABLE READ와 READ COMMITTED의 차이는 언두 영역에 백업된 레코드의 여러 버전 가운데 몇 번째 이전 버전까지 찾아 들어가야 하는지에 있다고 한다.

 

 

 

DB 동시성 관련 해결책 중 레디스를 사용한 분산 락 방법 에 대해 알아보겠다.

 

분산락 (Distributed lock)

DB의 하나의 공유 자원에 대한 경쟁 상황에서 데이터에 접근할 때, 데이터의 결함이 발생하지 않도록 원자성(atomic)을 보장하는 기법이다.

  • 서버가 여러 대인 상황에서 동일한 데이터에 대한 동기화를 보장하기 위해 사용한다.
  • 서버들 간 동기화된 처리가 필요하고, 여러 서버에 공통된 락을 적용해야 하기 때문에 redis 를 이용하여 분산락을 이용한다.
  • 분산락 같은 경우 공통된 데이터 저장소(DB)를 이용해 자원이 사용중인지 확인하기 때문에 전체 서버에 동기화된 처리가 가능하다.

 

하나의 DB의 두 명의 Client_A, Client_B  가 요청을 했을때, Client_A 가 락 을 획득하여 소지 중일때는 Client_B 는 요청은 했지만, 락은 획득하지 못하여 락 획득 대기중이다.

 

이때 Client_A가 락을 반납하면 Client_B 가 락을 가져오는데 이때  pub/sub, 혹은  스핀락 방식으로 락을 획득한다.

 

 

한 번 실습을 해보도록 하겠다.

 

Redisson RLock 라이브러리를 지원한다.

 

boolean tryLock(long waitTime, long leaseTime, TimeUnit timeUnit) throws InterruptedException;

 

  • waitTime: 락 획득을 위해 기다리는 시간
  • leaseTime: 락을 임대하는 시간
  • timeUnit: 시간 단위
  1. waitTime: 잠금을 시도하는 최대 시간. 이 시간 동안 잠금이 가능해질 때까지 기다린다
  2. leaseTime: 잠금이 성공했을 경우, 잠금이 유지되는 시간. 이 시간이 지나면 잠금이 자동으로 해제된다.
  3. timeUnit: waitTime과 leaseTime의 시간 단위를 지정. TimeUnit은 나노초, 마이크로초, 밀리초, 초, 분, 시간, 일 등 여러 시간 단위를 지원.

 

 

 

@Bean
public RedissonClient redissonClient(){
    log.info("RedissonClient 등록");
    Config config = new Config();
    config.useSingleServer().setAddress("redis://" + host + ":" + port);
    log.info("config : {}", config);
    return Redisson.create(config);
}

 

Client Config 생성 해준다.

 

서버주소나, 비밀번호, 타임아웃, 서버모드 등을 설정할 수 있다.

 

Config 객체를 선언하는 주요 이유는 Redisson 클라이언트의 동작을 구성하고 최적화하기 위해서이다. 이를 통해 Redis 서버와의 연결을 설정하고, 성능 및 보안 관련 설정을 일관되게 관리할 수 있으며, 다양한 Redis 배포 모드를 유연하게 지원할 수 있다. Config 객체를 사용하면 애플리케이션의 유지보수성과 확장성을 크게 향상시킬 수 있다.

 

게시판 프로젝트에서

postUpdate 부분에서 한번 사용을 해보려고한다.

public void update(long id, PostUpdateReqDto postUpdateReqDto) throws EntityNotFoundException {
        log.info("업데이트 시작");
        Post post = postRepository.findById(id).orElseThrow(EntityNotFoundException::new);

        String lockName = "MEMBER" + id;

        RLock lock = redissonClient.getLock(lockName);

        long waitTime = 5L;
        long leaseTime = 3L;
        TimeUnit timeUnit = TimeUnit.SECONDS;

        try {
            boolean available = lock.tryLock(waitTime, leaseTime, timeUnit);
            if (!available) {
                throw new IllegalArgumentException("락 획득 실패");
            }
//            락 획득후 수행하는 것
            post.updatePost(postUpdateReqDto.getTitle(), postUpdateReqDto.getContents());
            postRepository.save(post);

        } catch (InterruptedException i) {
            throw new IllegalArgumentException("락 얻으려고 했는데 뺏어감");
        } finally {
            try {
                lock.unlock();
                 log.info("unlock complete: {}", lock.getName());
            } catch (IllegalMonitorStateException e) {
                throw new IllegalArgumentException("이미 종료된 락입니다.");
            }
        }
    }

 

락의 이름을 MEMBER{id} 형식으로 설정하여 ID별로 고유한 락을 생성한다.

Redisson 클라이언트를 사용해 RLock 객체를 생성한다.

락을 획득한 후에 업데이트를 수행한다.

 

그리고 나서 락을 unlock을 해줘 놓아준다.

 

 

 

 

https://innovation123.tistory.com/185

 

[Redis] Redisson 분산 락을 간단하게 적용해보기

문제 상황 어떤 데이터에 대해 매우 빠르게 수정이 일어날 때 동시성 문제가 발생할 수 있다. 예를 들어 A라는 데이터를 수정하는 로직이 0.1초 소요되는데, 0.001초 간격으로 A라는 데이터를 수정

innovation123.tistory.com

https://velog.io/@soyeon207/DB-Lock-%EC%B4%9D-%EC%A0%95%EB%A6%AC-2-%EB%82%99%EA%B4%80%EC%A0%81-%EB%9D%BD%EA%B3%BC-%EB%B9%84%EA%B4%80%EC%A0%81-%EB%9D%BD-%EB%B6%84%EC%82%B0%EB%9D%BD-%EB%8D%B0%EB%93%9C%EB%9D%BD

 

[DB] Lock 총 정리 - 2 (낙관적 락과 비관적 락, 분산락, 데드락)

낙관적락, 비관적락, 분산락, 데드락에 대해서 알아보자

velog.io

https://sabarada.tistory.com/175

 

[database] 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)

안녕하세요. 오늘은 낙관적 락과 비관적 락의 개념에 대해서 알아보는 시간을 가져보도록 하겠습니다. DB 충돌 상황을 개선할 수 있는 방법 database에 접근해서 데이터를 수정할 때 동시에 수정이

sabarada.tistory.com

https://redis.io/docs/latest/

https://ryu-e.tistory.com/9

반응형

댓글