07 LOCK
07 LOCK
1. Lock - Transaction
1. Transaction
- DB의 일관성과 무결성을 보장하기 위한 단위를 정의
- 트랜잭션 핵심에 따른 단위/정의
- A: 원자성 → 전부 성공/전부 실패
- C: 일관성 → 데이터 규칙이 깨지지 않음
- I: 고립성 → 동시에 실행되어도 논리적으로 독립적으로 보임
- D: 지속성 → 커밋된 데이터는 유지됨
- 트랜잭션은 동시성 문제를 해결하는 개념
2. Lock
- DB에서 같은 데이터에 여러 커넥션이 접근할 때 충돌이 나지 않도록 잠그는 것
- DB에서 제공하는 락은 두종류
- 공유 락 (SELECT FOR SHARE, 읽는 동안 다른 사람이 쓰지 못함)
- 배타 락 (SELECT FOR UPDATE, 쓰는 동안 다른 사람이 읽지도, 쓰지도 못함)
2. 락을 이용하는 설계/전략(기법)
1. DB 내부 락 기반 방식
- DB는 기본적으로 MVCC(멀티 버전 동시성 제어)를 사용함.
- 같은 데이터를 읽어도 각각의 트랜잭션은 자기 시점에 유효한 데이터를 읽음
- 종류
| 방식 | 설명 | 공통 특징 |
|---|---|---|
| 낙관적 락 (Optimistic Lock) | 버전 등으로 충돌을 감지 | row 자체 잠금은 없음 |
| 비관적 락 (Pessimistic Lock) | SELECT FOR UPDATE 로 row를 직접 잠금 | 락을 DB가 직접 유지 |
1. 낙관적 락 작동방식
- 충돌을 미리 막지 않고, 나중에 감지해서 처리하는 방식
- 순서
- 데이터를 조회할 때 락을 걸지 않음
- 엔티티/레코드에 version 칼럼을 둠(★)
- 업데이트 시 WHERE id = ? AND version = ? 조건으로 실행 ``` – 동시에 2개(A,B) AP가 조회 select * from seat where id = 10 (seat_count 5 // version 1)
– 동시에 2개(A,B) AP가 작업 UPDATE seat SET seat_count = 4, version = version + 1 WHERE id = 10 AND version = 1;
– 먼저 작업한 A는 성공, B는 실패(일치하는 version이 없음) – B는 별도 재작업 로직 설정필요. ```
- 업데이트된 경우 version 증가
- 만약 동시에 변경되어 version이 다르면 업데이트 실패 → 예외 발생
- 사용하는 타이밍
- 재고 감소 (쇼핑몰)
- 좌석 수 감소 (잔여석 카운트만 관리하는 형태)
- 대부분 읽고, 가끔만 write 발생하는 환경
2. 비관적 락 작동방식
- 데이터 조회 시점에 바로 DB에게 잠금을 요청함.
- PostgreSQL 기준
1 2 3 4 5
-- 트랜잭션이 해당 row를 먼저 점유, 다른 트랜잭션은 해당 row에 접근하려고 하면 대기(block) 되거나 에러 발생. SELECT * FROM seat --seat 테이블 WHERE id = 10 -- 10번을 FOR UPDATE;
2. 애플리케이션/아키텍처 레벨에서 동시성 제어 방식
| 방식 | 설명 | 공통 특징 | | ————————- | —————————————– | —————- | | Unique Constraint + Retry | WHERE remain > 0 같은 조건과 unique 조건으로 원자 처리 | 재시도 로직은 앱/서비스 레벨 | | 임시 홀드 테이블 | 임시 예약 테이블 두고 TTL 만료처리 | 사용자 경험 중심 구조 | | 분산 락 (Redis 등) | 네트워크 기반 락(key 단위) | 다중 서버 환경에서 유효 | | 메시지 큐 직렬 처리 | 요청을 큐에 넣고 소비자가 하나씩 처리 | 동시성 자체를 제거한 설계 |
1. Unique Constraint + Retry
- DB에서 조건문과 제약(Unique/WHERE) 을 이용해 원자적 업데이트를 수행
- 충돌 발생 시 애플리케이션에서 재시도
- 방식
1 2 3 4 5
-- 남은 좌석(remain) 1개 이상이면 감소 -- 낙관적락에서는 버전이 일치해야하지만, 이 방식에서는 특정 조건 만족하면 바로 적용 UPDATE seat SET remain = remain - 1 WHERE id = 10 AND remain > 0;
2. 임시 홀드 테이블
- 실제 확정(DB 반영) 전에 임시 테이블에 데이터를 저장
- TTL(Time-To-Live)을 설정해 일정 시간 지나면 자동 해제
- 사용자 경험(UX) 중심 설계: “선점된 좌석이 몇 분간 보장됨”
- 방식
- 동작 방식
- 사용자가 좌석 선택 → 임시 홀드 테이블에 insert
- TTL(예: 5분) 동안 좌석을 다른 사용자 접근에서 잠금 효과
- 사용자가 결제 완료 → 실제 좌석 테이블에 반영, 임시 테이블에서 삭제
- TTL 만료 → 자동 삭제 → 다른 사용자가 사용 가능
1 2 3 4 5 6 7
CREATE TABLE seat_hold ( id BIGSERIAL PRIMARY KEY, seat_id BIGINT NOT NULL, user_id BIGINT NOT NULL, hold_expires_at TIMESTAMP NOT NULL, -- 만료시간 / 초과시 삭제 필요 created_at TIMESTAMP DEFAULT now() );
3. 분산 락 (Redis 등)
- 멀티 서버 환경에서 동일 자원(좌석, 재고 등)에 접근할 때 동시성 문제를 제어하는 기법
- Redis, Redisson, Zookeeper 등 네트워크 상 공유 저장소를 활용
- DB 락보다 가볍고, 트랜잭션 시간을 길게 잡지 않아도 됨.
- 임시 홀드테이블과 동일한 패턴이지만, 테이블이 아닌 메모리에 저장해서 더 빠르게처리함.
4. 메시지 큐 직렬 처리
- 동시성 자체를 구조적으로 제거하는 방식
- 모든 요청을 메시지 큐에 넣고 소비자가 순차적으로 처리
- 경쟁이 아예 발생하지 않도록 설계하는 전략
- 방식
- 사용자가 좌석/재고 요청 → 메시지 큐(Kafka, RabbitMQ 등)에 발행
- 큐 소비자(Consumer)가 메시지를 하나씩 처리
- DB 업데이트, 알림 전송 등 모든 처리를 순차적으로 수행
- 처리 완료 후 사용자에게 응답
This post is licensed under CC BY 4.0 by the author.