[백엔드] 락을 통해 동시성 해결하기 - 모니터락, DB락, 분산락(레디스)

2026. 4. 21. 21:55·CS/백엔드

동시성 처리하기

어플리케이션에서 해결하기 - 모니터락

자바에서 모든 객체들은 내부에 락을 하나씩 가지고 있는데, 이 락을 모니터락이라고 칭한다. 자바에서 synchronized 키워드를 사용한 부분(메서드 또는 블럭)에 도달하면 모니터락을 획득해야 임계영역에 들어갈 수 있다. synchronized 키워드를 실행할 때 락을 획득하고, 임계영역의 작업을 수행한 뒤, 다시 락을 반납하고 나오는 방식이다.

자바에서 가장 기본적으로 제공하는 락 방식이며, 한 번에 한 개의 스레드만 사용할 수 있다.


synchronized는 만능이 아니다

우선 가장 큰 단점은 분산시스템에서는 사용하지 못한다. 스프링에서 객체는 싱글턴으로 생성되지만 그게 모든 서버에 공유되는 하나의 객체가 아니기 때문이다.

 

또한 @Transactional 어노테이션을 사용하지 못하는데, 이는 Spring AOP에서 기인한 것이다. 스프링 AOP는 프록시 객체를 만들어 원래 객체의 메서드를 실행시키고 난 후에 트랜잭션을 커밋시키는데, 이 때 프록시 객체는 synchronized 처리가 되어있지 않으므로 (메서드 시그니처에 synchronized 키워드는 해당되지 않는다) 이전 클라이언트가 락을 반납한 후 커밋을 날리기 전에 락을 취득한 객체는 커밋 이전의 데이터를 보게된다. 이를 갱신손실이라고 칭하며, synchronized만 사용해 DB에 접근하면 이를 막을 수 없다.

 

이처럼 synchronized는 분산환경에서 사용할 수 없고, 트랜잭션도 사용할 수 없고, 한 번에 한 스레드만 락을 획득할 수 있어 성능저하도 있기 때문에 잘 안쓰인다고한다. 따라서 이를 위해 DB 계층에서 락을 걸어주는 DB락이 있다.


낙관적락

@Entity
public class BaseEntity {

  @Id
  private String id;
  
  private String name;

  @Version
  private Integer version;
}
public interface BoardRepository extends JpaRepository<Board, String> {

    @Lock(LockModeType.OPTIMISTIC)
    @Query("select b from Board b where b.id = :id")
    Optional<Board> findByIdForUpdate(@Param("id") String id);
}

낙관적(Optimistic) 락은 트랜잭션 충돌이 일어나지 않을 것으로 "낙관적"으로 가정하는 방법이다. 엔티티 수준에서 동시성을 제어하는 방법으로, 모든 엔티티들은 필드에 version 필드를 가진다. 이후 DB에 커밋을 날릴 때마다 엔티티의 version 값이 변하며, 커밋 시 엔티티의 버전과 DB에 저장된 엔티티의 버전을 비교해 충돌여부를 조사한다.

 

public void does(){
    for (;;) {
        try {
            reservationService.reserve();
            break;
        } catch (Exception e) {
            sleep(1000);
        }
    }
}

public void sleep(int time){
    try {
        Thread.sleep(time);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

JPA에서는 @Version 어노테이션만 있으면 사용가능하며, 만약 동시성 충돌이 발생할 경우 에러가 발생하므로 추가 에러처리로직을 개발해야한다. 낙관적 락이 적용된 DB테이블에 접근하는 객체를 try-catch 문을 통해 호출하고, 예외처리도 추가로 진행해야한다. 위의 코드는 낙관적락을 사용해 충돌이 발생할 경우 스레드를 잠시 정지시킨 후 다시 락을 획득하도록 작업을 수행시키는 코드


비관적락

public interface BoardRepository extends JpaRepository<Board, String> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select b from Board b where b.id = :id")
    Optional<Board> findByIdForUpdate(@Param("id") String id);
}

비관적(Pessimistic) 락은 트랜잭션 충돌이 반드시 일어날 것으로 "비관적"으로 가정하는 방법이다. DB 수준에서 락을 통해 동시성을 제어한다.

 

모드는 크게

1. PASSIMISTIC_READ (읽기 잠금) - 읽기는 자유, 쓰기는 락 필요

2. PASSIMISTIC_WRITE (쓰기 잠금) - 쓰기, 읽기 모두 락 필요

가 있으며, 락에 획득하지 못하거나 너무 오래 대기하면 에러가 발생한다.


비관적? 낙관적?

그렇다면 비관적락을 택해야할까 낙관적락을 택해야할까? 이는 "동시에 수정이 빈번하게 일어나는가?"를 기준으로 삼을 수 있다. 만약 동시수정이 적다면 낙관적락을, 그렇지 않다면 비관적락을 사용하는 것이 적절하다.

 

낙관적락은 동시성 오류가 발생하지 않을 때까지 계속해서 락을 획득하는 요청을 반복하기 때문에, 그리고 비관적락은 성능부하가 다소 심하기 때문에 동시에 수정이 자주 일어나지 않은 경우에만 가벼운 낙관적락을 사용하는 것이 좋다.


DB락은 만능이 아니다

비관적락을 사용하고, DB까지 분산시스템을 적용하는 경우, 락이 다른 노드까지 전달되지 않아 다시 데이터 정합성 문제가 발생하 ㄹ수 있다. 따라서 사용자와 DB 사이에서 락을 총괄하는, 징검다리 역할이 필요하다. 그리고 이 징검다리가 관리하는 락을 분산락이라고 칭한다. 대표적으로 레디스가 있다.

 

레디스를 분산락으로 사용하면 리소스에 접근하려할 때 리소스 식별자를 기반으로한 키를 등록한다. 만약 키를 등록하고자했는데 레디스에 이미 같은 키가 등록되어있다면 재시도하는 방식. 작업이 끝났다면 등록한 키를 지워 락을 반납하며, 키에 서명을 넣어 키를 등록한 클라이언트만이 키를 지울 수 있도록한다.


1개의 레디스는 만능이 아니다

한 개의 레디스 서버는 레디스 서버에 장애가 발생한 경우 모든 서비스가 멈춰버리는 단일 장애 지점(SPOF, Single Point Of Failure)이 될 수 있다. 따라서 여러 대의 레디스 서버를 사용해야한다.

 

다만 락을 다루고 있기 때문에 일반적인 분산DB의 master-slave 구조를 사용할 수는 없다. master 서버의 락을 취득한 후 레플리카 DB에 전해주기 전에 master 서버에 장애가 발생한 경우 기존 slave DB가 master DB로 올라서게되는데, 이렇게되면 획득한 락이 사라지기 때문이다. 따라서 레디스에서는 RedLock 알고리즘을 사용한다.

 

RedLock 알고리즘

1. 클라이언트는 N대의 레디스 서버에 순차적으로 락을 요청한다. 이때 유효시간은 아주 짧게 설정한다.

 

2. 레디스 서버로부터 응답을 받은 후, 유효시간 내 락을 성공적으로 획득한 레디스 서버의 수를 측정한다. 이 중 과반수 이상의 레디스 서버로부터 락을 획득했다면, 락 획득에 성공한 것으로 본다.

2-1. 최종적으로 락 획득에 실패한 경우, 클라이언트는 락 획득에 성공한 서버들에게 락 해제 요청을 보낸다.

2-2. 최종적으로 락 획득에 성공했으나, 락 획득에 실패한 과반수 이외이 레디스 서버들은 아무런 동작도 수행하지 않는다. 이미 과반수의 레디스 서버에서 락이 획득당했기 때문에 다른 클라이언트가 동시에 락을 획득할 염려가 없다.


RedLock도 만능은 아니다

Clock Drift

단일 서버에서 synchronized를 사용하면 애플리케이션 전반에 똑같이 적용되는 클럭을 사용하므로 문제가 없지만, 분산시스템에서는 그렇지 않다. 그리고 만약 레디스 서버 간 클럭에 오차가 생기면 다른 노드들의 락이 해제되기 전에 락이 해제되는 노드가 발생할 수 있다,

 

1. 5개의 노드 중에서 RedLock 알고리즘을 통해 3대의 노드에게서 락을 획득한다.

 

2. 3대의 노드 중 한 대의 노드에서 클럭 드리프트가 발생해 혼자서 락이 해제되었다.

 

3. 다른 클라이언트가 락 요청을 보냈고, 현재 락을 획득 중인 2대의 노드 이외의 3대의 노드에게서 락을 획득한다. 즉, 락을 획득한 클라이언트가 2대가 된다.

 

애플리케이션 중단

클럭 드리프트는 낮은 확률로 발생하지만, 애플리케이션 중단의 경우 자주 일어나면서도 클럭 드리프트와 비슷한 결과가 발생한다.

 

1. 클라이언트A가 락을 획득한다.

 

2. 클라이언트A가 작업을 수행하다 애플리케이션이 중단되었고, 분산락 유효시간이 만료된다.

 

3. 클라이언트B가 락을 획득하고 작업을 수행한다. DB에도 커밋을 날린다.

 

4. 클라이언트A의 문제가 해결되었고, 마저 작업을 수행한 후 DB에 커밋을 날린다.

 

5. 클라이언트A가 커밋한 내용과 클라이언트B가 커밋한 내용이 다르다. 즉, 동시성 문제가 발생한다.

 

가비지컬렉터 중에서 stop-the-world 방식을 사용하는 가비지컬렉터의 경우에는 GC 중에도 같은 일이 발생할 수 있다.

이를 해결하기 위해서 커밋 시점에서 버전을 검사하는, 낙관적 락과 비슷한 알고리즘을 사용하거나 펜싱토큰(fencing token)을 사용해야한다. 펜싱토큰은 레디스가 제공하지 않는다.

'CS > 백엔드' 카테고리의 다른 글

[백엔드] 도전! 실전 인증인가  (0) 2026.05.27
[백엔드] 스프링 컴포넌트 스캔과 Bean 등록하기 및 DI  (0) 2026.03.16
[백엔드] 인증요청 시의 SecurityFilterChain의 동작과정  (0) 2026.02.18
[백엔드] 로그인 시의 SecurityFilterChain의 동작과정  (0) 2026.02.16
[백엔드] Spring Security 첫 걸음 - DelegatingFilterProxy, FilterChainProxy로 Web Context Filter에 Spring Bean Filter 등록하기  (0) 2026.02.14
'CS/백엔드' 카테고리의 다른 글
  • [백엔드] 도전! 실전 인증인가
  • [백엔드] 스프링 컴포넌트 스캔과 Bean 등록하기 및 DI
  • [백엔드] 인증요청 시의 SecurityFilterChain의 동작과정
  • [백엔드] 로그인 시의 SecurityFilterChain의 동작과정
컬러잇
컬러잇
탄천러너지망생
  • 컬러잇
    Color it
    컬러잇
  • 전체
    오늘
    어제
    • 분류 전체보기 (235)
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (72)
        • JVM (12)
        • 인프라 (5)
        • 백엔드 (22)
        • 논리회로 (5)
        • 언어구현 (1)
        • 인공지능 (1)
        • 코드설계 (3)
        • 컴퓨터구조 (9)
        • 데이터베이스 (4)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (65)
        • Java | Kotlin (49)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (4)
      • 고찰 (35)
        • CEOS 23기 회고록 (9)
  • 링크

    • 교양있는컬러잇
  • 최근 글

  • 인기 글

  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.5
컬러잇
[백엔드] 락을 통해 동시성 해결하기 - 모니터락, DB락, 분산락(레디스)
상단으로

티스토리툴바