웹 개발이나 API 설계에서 동시성 문제는 피할 수 없는 주제입니다. 특히 시뮬레이션 실행이나 계좌 잔액 수정처럼 동시에 여러 요청이 들어오는 상황에서는 데이터 정합성을 유지하는 것이 중요합니다. 이를 위해 흔히 사용하는 방법이 락(Lock) 전략입니다. 이번 글에서는 **낙관적 락(Optimistic Lock)**과 비관적 락(Pessimistic Lock), 그리고 분산 락(Redis 락) 개념을 정리합니다.
1. 비관적 락(Pessimistic Lock)
원리:
- 데이터를 조회하거나 수정할 때 즉시 잠금
- 다른 트랜잭션이 해당 데이터를 수정하지 못하도록 차단
장점:
- 동시성 충돌 방지
- 데이터 정합성 강력 보장
단점:
- 락이 유지되는 동안 다른 트랜잭션은 대기 → 성능 저하
- 데드락 가능
DB 적용 예 (PostgreSQL + SQLAlchemy):
from sqlalchemy import select
execution = db.execute(
select(SimulationExecution)
.where(SimulationExecution.id == 1)
.with_for_update()
).scalar_one()
주의: 단일 DB 연결에서는 안전하지만, 멀티 서버 환경에서는 다른 서버의 DB 연결에는 락이 적용되지 않을 수 있습니다. 따라서 Redis 같은 분산 락을 함께 사용해야 안전합니다.
2. 낙관적 락(Optimistic Lock)
원리:
- 데이터를 자유롭게 조회(잠금 없음)
- 수정 시점에 버전(version) 또는 타임스탬프(timestamp) 비교
- 충돌이 발생하면 업데이트 실패 후 재시도
장점:
- 잠금을 오래 유지하지 않아 성능 유리
- 충돌 가능성이 낮은 데이터 처리에 적합
단점:
- 충돌이 잦으면 재시도 빈도 증가 → 성능 저하 가능
DB 컬럼 예:
version = Column(Integer, default=1)
동작 예시:
- 트랜잭션 A와 B가 같은 레코드 조회
- A가 먼저 수정 후 커밋 → 버전 증가
- B가 수정 시도 → 버전 불일치 → 실패 → 재시도 필요
즉, “서로 자유롭게 읽고, 마지막에 저장할 때 충돌 체크”하는 방식입니다.
3. Redis 분산 락
원리:
- 여러 서버 환경에서 동시에 같은 리소스 접근을 막기 위해 사용
- API/애플리케이션 레벨에서 잠금 처리
동작 예시:
if redis.set(lock_key, "locked", nx=True, ex=30):
# 락 획득 → 작업 수행
redis.delete(lock_key) # 락 해제
else:
raise HTTPException(409, "이미 실행 중")
특징:
- 실제 잠금 방식 → 비관적 락과 유사
- 단일 DB만 사용하는 비관적 락과 달리 멀티 서버 환경에서 동시성 제어 가능
4. 언제 어떤 락을 사용할까?
락 유형 | 사용 상황 | 장점 | 단점 |
비관적 락 | 충돌 가능성 높음 | 정합성 강력 | 성능 저하, 데드락 가능 |
낙관적 락 | 충돌 가능성 낮음 | 성능 좋음 | 충돌 시 재시도 필요 |
Redis 분산 락 | 멀티 서버 환경 | 안전한 동시성 제어 | 외부 시스템 의존 |
실무에서는 비관적 락 + Redis 분산 락 병행하여
- DB 내부 정합성 확보
- 멀티 서버 동시 요청 제어
둘 다 안전하게 처리하는 패턴을 사용합니다.
5. 마무리
- 비관적 락: “먼저 잠근다”
- 낙관적 락: “나중에 확인한다”
- Redis 분산 락: “서버 간에도 잠금 정보를 공유한다”
이 세 가지를 이해하고 적절히 설계하면, FastAPI와 SQLAlchemy 환경에서 시뮬레이션 실행이나 계좌 처리 등 동시성 문제를 안정적으로 해결할 수 있습니다.
'컴퓨터 과학 > 데이터베이스' 카테고리의 다른 글
트랜잭션의 연산 중 COMMIT의 개념을 간략히 설명해주세요. (0) | 2022.08.06 |
---|