웹 개발이나 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)

동작 예시:

  1. 트랜잭션 A와 B가 같은 레코드 조회
  2. A가 먼저 수정 후 커밋 → 버전 증가
  3. 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 환경에서 시뮬레이션 실행이나 계좌 처리 등 동시성 문제를 안정적으로 해결할 수 있습니다.

복사했습니다!