byworld 님의 블로그
20260402 TIL - PR, 동시성 제어, 낙관/비관 락 본문
[서론]
오늘은 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 분산)에 따라 도구가 달라진다.
'TIL' 카테고리의 다른 글
| 20260408 2차 프로젝트 끝 (1) | 2026.04.08 |
|---|---|
| 20260403 TIL - 인증인가 개발, AWS 비용 (0) | 2026.04.03 |
| 20260401 TIL - 클로드 코드 유출 (0) | 2026.04.01 |
| 20260331 TIL - 공통 모듈 구축 (0) | 2026.03.31 |
| 20260330 TIL - 클로드 코워크, GW 구축 (0) | 2026.03.30 |