- observability
- airflow
- 쿠키
- 정합성
- 세션
- nextjs
- til
- PostgrSQL
- DBT
- HTTP
- db
- httpOnly
- 백엔드
- 크루스칼
- MST
- NoSQL
- react
- 플로이드워셜
- BigQuery
- de
- flink
- 프론트엔드
- Frontend
- 네트워크
- SSAFY
- websocket
- SQL
- DeltaLake
- isr
- Spark
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Today
- Total
R+7
[TIL] 차근차근 데이터 (2) - 낙관적 락과 비관적 락 본문

Today I Learned
DB에 접근해 데이터를 수정할 경우 충돌 문제가 발생할 수 있다. 이 경우 두가지 해결책이 있다. 테이블 row에 접근시 락을 걸고 다른 락이 걸려 있지 않을 경우에만 수정을 가능케 하는 방법, 그리고 락을 따로 걸지는 않지만 저장 시점에서 내가 읽은 후 다른 사람이 값을 바꾸지는 않았는지 체크하고 바뀌었다면 갱신을 거부하는 방법이다.
전자는 비관적 락, 후자는 낙관적 락이다. 둘의 차이는 충돌을 언제 다루는지에 대한 문제다. 비관적으로 판단해 충돌을 방지하고자 미리 잠그는 방식과, 낙관적으로 판단해 충돌은 드물거라 여기고 일단 진행한 다음 버전을 비교해 사후 검출하는 방식인 셈이다.
비관적 락
비관적 락(Pessimistic Lock)은 DB의 물리적 락을 이용하는 동시성 제어 기법이다. 이름 그대로 충돌이 발생하리라 '비관적'으로 가정해 데이터 조회 시점부터 해당 데이터에 락을 걸어 다른 트랜잭션이 접근하지 못하도록 차단하는 방식이다.
일반적인 조회문(SELECT)은 락을 걸지 않지만, 명시적으로 SELECT ... FOR UPDATE를 걸면 배타 락(Exclusive Lock, 쓰기 락)을 획득할 수 있다. 한 트랜잭션이 배타 락을 잡으면, 다른 트랜잭션이 같은 행에 대해 배타 락을 잡거나 수정(UPDATE/DELETE)하려는 시도가 Blocking(대기 상태)에 빠진다. 이때 다른 트랜잭션의 일반 SELECT는 여전히 통과하는데, 이는 배타 락이 조회를 "허용"해서가 아니라, 일반 조회가 MVCC 기반으로 스냅샷을 읽어 애초에 락을 요구하지 않기 때문이다. 락을 쥔 트랜잭션이 수정을 마치고 Commit 또는 Rollback을 수행해야 락이 해제된다.
SELECT ... FOR SHARE는 공유 락(Shared Lock, 읽기 락)을 거는 구문이다. 여러 트랜잭션이 동시에 가질 수 있고 서로를 막지 않지만, 이 행을 수정(UPDATE/DELETE)하거나 배타 락을 잡으려는 시도는 막는다.
데드락(Deadlock)이란 두 개 이상 트랜잭션이 서로 점유한 락이 해제되기를 무한정 기다리는 교착 상태로, 이를 예방하기 위해서는 자원 접근 순서를 동일하게 통제하거나, 트랜잭션 단위를 최소화하거나 (복잡한 계산은 외부로 분리), 락 타임아웃을 설정하거나, 정확한 인덱스를 활용해야 한다. 인덱스 사용은 직관적으로 이해가 잘 안갈 수 있을텐데, 이는 쿼리 조건절에서 인덱스가 없는 컬럼 사용시 특정 행이 아닌 불필요하게 스캔하면서 지나가는 다른 행들에까지 락이 걸릴 수 있기 때문. 인덱스를 정확히 타도록 쿼리를 튜닝해 꼭 필요한 데이터 row에만 락이 걸리도록 제한해야 한다.
낙관적 락
낙관적 락(Optimistic Lock)은 반대로 충돌이 발생하지 않으리라 '낙관적'으로 가정하는 방식이다. DB 수준의 물리적 락을 사용하지 않고, 애플리케이션 수준에서 데이터의 버전 또는 타임스탬프를 확인해 갱신(커밋) 시점에서 충돌 여부를 감지한다. 데이터베이스에서 대기열을 만드는 것이 아니라 애플리케이션(서버) 단에서 예외를 발생시키는 방식이다. 사실상 자물쇠가 아니고, 저장 직전 검문소에 가까운 개념. 추가로, 타임스탬프 기법은 시계 동기화 이슈가 있어 실무에서는 대체로 정수 버전을 선호한다고.
다만 데이터 충돌이 잦은 환경에서 사용하는 것은 권하지 않는다고 한다. 충돌이 발생하면 작업을 처음부터 다시 수행하게 되는데 (데이터 재조회 → 비즈니스 로직 재실행 → 커밋 시도 → 버전 불일치로 실패 → 다시 재조회...) 루프가 반복되며 서버 스레드를 불필요하게 점유하게 된다. 또한 커밋 실패시 서버가 수행한 모든 연산, 쿼리, 통신 자원이 헛되이 버려지기 때문.
동시성/상호배제를 푸는 방식
외부 인메모리 저장소 Redis를 이용해 락을 획득·반환하는 Redis 분산 락은, 상호배제의 책임을 DB가 아니라 애플리케이션 계층으로 옮긴 방식이다. 비관적 락이 단일 DB 내 스스로 상호배제를 보장하는 것과 달리, 애플리케이션 서버가 여러 대로 늘어나면(스케일아웃) 서버 간 공유 상태가 없어 상호배제가 깨진다. 이때 모든 서버가 공통으로 바라보는 Redis에 락을 둬서, 여러 애플리케이션 서버(프로세스) 간의 상호배제를 구현한다.
Kafka나 RabbitMQ 같은 큐에 쌓아두고 Worker가 순차적으로 하나씩 꺼내 처리하도록 설계하는 메시지 큐도 알아두면 좋다. 큐는 애초에 동시가 안 되게 한 줄로 세우는 직렬화 작업이다. 충돌이 발생할 상황 자체를 없애는 패러다임인 셈.
Today I Listend to
'TIL' 카테고리의 다른 글
| [TIL] 차근차근 CS (7) - 웹소켓 (0) | 2026.07.16 |
|---|---|
| [TIL] 차근차근 CS (6) - 흐름 제어 기법 (0) | 2026.07.15 |
| [TIL] 차근차근 CS (5) - Keep-Alive (0) | 2026.07.13 |
| [TIL] 차근차근 CS (4) - DB의 역사와 발전 과정 (0) | 2026.07.11 |
| [TIL] 차근차근 CS (3) - Backend 역사와 발전 과정 (0) | 2026.07.10 |