byworld 님의 블로그

20260402 TIL - PR, 동시성 제어, 낙관/비관 락 본문

TIL

20260402 TIL - PR, 동시성 제어, 낙관/비관 락

byworld 님의 블로그 2026. 4. 2. 21:00

 

[서론]

오늘은 PR을 하루종일 하고 스터디도 진행했다. 내 구현도 해야하는데... 좀 자고 나서 해야겠다.

 

[오늘의 추천곡] Travis Scott - Fe!n

콘서트에서 1.3 지진이 났단 그 곡이다.(이후 테일러 스위프트 콘서트가 2.3 지진을 내기도 했다) 이런 것을 보면 공명과 동기화라는 물리현상이 실제로 일어난다는 것을 알 수 있다.

트레비스 스캇은 분노(Rage)를 곡으로 쓴 가수이다. 지금 하고 있는 MSA 에서 api 주는 것이 Feign인데 곡 이름이 비슷해서 가져왔다. 그건 그렇고 유달리 그래미 어워즈가 트래비스한테만 각박하다.

 

 

 

 

 

[PR]

오늘 팀원들이 개발들을 좀 진행했다. 그래서 PR 봐줄 사람이 필요한데, 다들 바쁜 것 같아서 내가 거의 맡아서 했다. 물론 나도 해야할 것이 많지만 그래도 이게 제일 효율이 나올 것 같았다. Order, Item, Company, GW, Delivery 도메인을 다 봤다. 다 처음 올리는 거라 각자 500-1000줄정도 되더라. 눈이 아프다. 그래도 도움이 됐으면 한다. 오히려 이게 나중에 MSA를 제대로 할 때 도움이 많이 될 것 같다. 어떤 관점으로 했는지 보이기 때문이다. 그래도 피드백 받는 것 보다 이게 난 낫다. 솔직히 피드백 들으면 인터럽트 되는 기분이 조금 들긴 한다.

 

[낙/비관락 & 동시성 제어]

이건 스터디원이 노션에 적고 설명했던 내용을 나만의 설명을 추가해 가져오겠다.

 

일단 낙관락과 비관락 표이다.

 

구분 비관락 낙관락
생각 방식 충돌이 날 것 같으니 미리 막자 충돌이 잘 안 날 것 같으니 나중에 확인하자
동작 방식 데이터를 가져올 때 락을 건다 저장할 때 version을 비교한다
장점 안전하다 성능에 유리하다
단점 대기 시간, 데드락 위험 충돌 시 재시도 필요
잘 맞는 상황 재고 차감, 좌석 예매, 계좌 잔액 게시글 수정, 회원정보 수정

 

낙관락은 Version으로 관리하고, 수정하려고 하는 버전이 맞으면 업데이트를 하고 수정하려했던 버전이 아닌 경우(버전업이 된 경우)에는 Exception 처리를 한다. 비관락은 물리적으로 락을 걸고, 한 락이 끝날때까지 동시에 들어온 요청은 대기한다. 이 race condition이 락을 안풀면 계속 정체 되므로(데드락) 일정 시간이나 조건에 따라 락을 풀어줘야한다. 그래서 그냥 Exception 처리하는 낙관 락이 성능 상 낫고, 비관락은 크리티컬한 부분을 막기 좋다. 재고 차감을 비관락 잘 맞는 상황이라고 했는데, 크리티컬하지 않고 트래픽이 많으면 낙관락을 고려해보는 것도 좋다.

 

조회같은 경우에는 데이터가 바뀌지 않으므로 @Transactional(readOnly = true)으로 트랜잭션을 관리할 수 있다. 이것을 설정하면 변경 감지(Dirty Check) 비용이 감소한다.

 

비관락이어도 모든 작업이 차단되는 것이 아니고 다른 처리를 할 수 있다. MVCC (Multi-Version Concurrency Control)를 사용하는 데이터베이스 같은 경우는 읽기는 락 없이 과거 버전을 본다. 그래서 비관락이 걸려도 row에 쓰기 락이 걸린다. 이 때 update는 불가하지만 select는 된다. 이전 버전 스냅샷을 읽기 때문이다. 읽기 시 데이터 정합성이 치명적이지 않을 때(최신 버전이 아니어도 괜찮은 경우) 사용하면 되겠다.

 

[동시성 제어]

이건 나의 노션을 가져오겠다.

 

문제 상황

여러 스레드가 동시에 같은 데이터에 접근하면 값이 꼬인다 (race condition).

해결 방법 2축

  • JVM 내부 (단일 서버): synchronized / Lock / Atomic / volatile
  • 서버 여러 대 (분산 환경): Redis 분산락

핵심 비교

기술 보장 범위 특징
synchronized 원자성 (atomicity) JVM 내부 쉬움, 느릴 수 있음
volatile 가시성 (visibility) JVM 내부 원자성 없음
Atomic 원자성 (atomicity) JVM 내부 빠름
ReentrantLock 원자성 (atomicity) 개발자 관리 유연함(타임아웃/조건변수/인터럽트 제어
Redis Lock 원자성 (atomicity) 서버 전체 분산 환경

0. Atomic 설명 보강

지금 Atomic 설명:

 

CAS( Compare-And-Swap ) 기반

 

한계

  • ABA 문제 (A -> B -> A 로 바뀌면 변경 안 된 것처럼 보이는 문제 감지 못함)
  • 반복 재시도 (spin) 비용

    경쟁이 심한 환경에서는 CAS 반복으로 성능이 저하될 수 있다.

 

1. synchronized

개념

가장 기본적인 락 (mutex)

synchronized void increase() {
    count++;
}

특징

  • 한 번에 한 스레드만 접근
  • JVM 레벨에서 관리
  • 자동 lock/unlock

단점

  • 블로킹 → 성능 저하 가능
  • 세밀한 제어 불가

언제 쓰냐

간단한 임계영역 보호


2. volatile

개념

메모리 가시성 보장 (최신값이 보이냐?)

happens-before 관계를 보장한다.

volatile boolean flag;

문제 해결

CPU 캐시 문제를 해결한다.

Thread A: flag = true
Thread B: flag 계속 false로 봄 (캐시 때문)

volatile을 쓰면 항상 메인 메모리에서 읽는다.

한계

원자성 없음

초기: count = 0

Thread A: count 읽음 → 0
Thread B: count 읽음 → 0   ← 이미 문제 시작

Thread A: 1로 증가 후 저장
Thread B: 1로 증가 후 저장

결과적으로  2를 기대했지만 1이 된다!

 

언제 쓰냐

상태 플래그 (on/off)


3. AtomicInteger

개념

락 없이 안전한 연산 (Compare-And-Swap 기반)

AtomicInteger count = new AtomicInteger();
count.incrementAndGet();

특징

  • non-blocking
  • 빠름
  • 내부적으로 Compare-And-Swap

상황 예시

상황 Count=0 주어지고, 스레드 2개

 

스레드 1 0→1 업데이트

스레드 2 0예상했는데 1이니까 실패처리하고 다시시도

스레드 2(재시도): CAS(1 → 2) 성공

 

결론 3으로 수정됨!

장점

 

  • synchronized보다 오버헤드가 적음
  • deadlock 없음
  • 높은 동시성 환경에서 효율적

한계

  • 반복 재시도(Spin Cost) -> 충돌 많으면 계속 실패 -> CPU 낭비 (성능 저하)
  • 복잡한 연산(계좌와 재고 동시 수정 등)에는 안전하게 처리 어려움 -> 락 필요!
  • 트랜잭션 단위 로직에서는 쓰면 안됨

 

 

언제 쓰냐

단순 카운터 / 숫자 업데이트


4. ReentrantLock

개념

synchronized 업그레이드 버전

lock.lock();
try {
    count++;
} finally {
    lock.unlock();
}

특징

  • JVM 내부에서 구현하기는 하지만 개발자가 직접 로직을 관리
  • tryLock() 가능
  • 공정성 설정 가능
  • interrupt 가능

장점

더 유연한 제어

 
lock.tryLock();
lock.lockInterruptibly();
 

이런 것도 가능

  • 타임아웃
  • 인터럽트 대응
  • 공정성 설정

단점

코드 복잡 (unlock 실수 위험)

개발자가 직접 설정해야함

언제 쓰냐

복잡한 동시성 제어


5. 분산락 (Redis / Redisson)

개념

서버 여러 대에서 락 공유. 서버 A와 서버 B가 동시에 요청하면 DB를 동시에 수정하게 되는데, JVM 락으로는 못 막는다.

해결

Redis에 락 저장

SETNX key → 락 획득

라이브러리

Redisson

한계

  • 락 잃어버림 (crash)
  • TTL 만료 문제
  • 네트워크 지연

그래서 Redisson은 watchdog으로 TTL 자동 연장

특징

  • 네트워크 기반
  • TTL(Time To Live) 필요 (락 죽는 문제 방지)
  • 성능 비용 있음

언제 쓰냐

MSA / 클러스터 환경


선택 기준 (실전)

  • 단순 증가 → Atomic
  • 간단 보호 → synchronized
  • 복잡 제어 → ReentrantLock
  • 상태 플래그 → volatile
  • 서버 여러 대 → Redis

핵심

동시성 제어는 같은 자원에 동시에 접근 못하게 막는 것이고, 문제 범위(JVM vs 분산)에 따라 도구가 달라진다.